diff --git a/PLUGIN_TYPES.md b/PLUGIN_TYPES.md
index aca9831..63c8ab4 100644
--- a/PLUGIN_TYPES.md
+++ b/PLUGIN_TYPES.md
@@ -19,10 +19,16 @@ on the class, but the default class takes the following:
 - operations_provider_class (optional): the fully qualified name of a class that
   implements `\Drupal\plugin\PluginType\PluginTypeOperationsProviderInterface`. 
   Defaults to `\Drupal\plugin\PluginType\DefaultPluginTypeOperationsProvider`.
+- plugin_configuration_schema_id (optional): the ID of the plugin's 
+  configuration schema. Two following replacement tokens are supported:
+  - [plugin_type_id]: The ID of the plugin's type.
+  - [plugin_id]: the plugin's ID.
+  Defaults to "plugin.plugin_configuration.[plugin_type_id].[plugin_id]"
 
 A configuration schema MAY be provided for all configurable plugin types. If a 
-schema is provided, its name MUST be like
+schema is provided, its name MAY be like
 `plugin.plugin_configuration.$plugin_type_id.$plugin_id`, where 
-`$plugin_type_id` is the plugin type ID as specified in 
+`$plugin_type_id` is the plugin type ID as specified in
 `$module.plugin_type.yml` and `$plugin_id` is the ID of a specific plugin, or an 
-asterisk (`*`)  to apply to all plugins of that type.
+asterisk (`*`)  to apply to all plugins of that type. This is recommended over 
+specifying a custom ID in the plugin type definition.
diff --git a/config/schema/plugin.schema.yml b/config/schema/plugin.schema.yml
index 087f2c8..cff8247 100644
--- a/config/schema/plugin.schema.yml
+++ b/config/schema/plugin.schema.yml
@@ -10,7 +10,10 @@ plugin.plugin_configuration.plugin_selector.plugin_selector_base:
     label:
       label: Label
       type: label
-    name:
+    description:
+      label: Label
+      type: label
+    required:
       label: Required
       type: boolean
 
@@ -28,13 +31,13 @@ plugin.plugin_configuration.*:
 "field.value.plugin:*":
   label: Plugin collection field value
   mapping:
-    plugin_type_id:
-      label: Plugin type ID
-      type: string
     plugin_id:
       label: Plugin ID
       type: string
     plugin_configuration:
       label: Plugin configuration
-      type: plugin.plugin_configuration.[%parent.plugin_type_id].[%parent.plugin_id]:
+      type: "[%parent.plugin_configuration_schema_id]"
+    plugin_configuration_schema_id:
+      label: Plugin configuration
+      type: string
   type: config_object
diff --git a/modules/plugin_test_helper/config/schema/plugin_test_helper.schema.yml b/modules/plugin_test_helper/config/schema/plugin_test_helper.schema.yml
new file mode 100644
index 0000000..0290d57
--- /dev/null
+++ b/modules/plugin_test_helper/config/schema/plugin_test_helper.schema.yml
@@ -0,0 +1,9 @@
+plugin_test_helper.plugin_configuration.plugin_test_helper_mock.*:
+  type: ignore
+
+plugin_test_helper.plugin_configuration.plugin_test_helper_mock.plugin_test_helper_configurable_plugin:
+  type: mapping
+  mapping:
+    foo:
+      label: A random string
+      type: string
diff --git a/modules/plugin_test_helper/plugin_test_helper.plugin_type.yml b/modules/plugin_test_helper/plugin_test_helper.plugin_type.yml
index 8dc7dc0..82bbe3b 100644
--- a/modules/plugin_test_helper/plugin_test_helper.plugin_type.yml
+++ b/modules/plugin_test_helper/plugin_test_helper.plugin_type.yml
@@ -1,3 +1,5 @@
 plugin_test_helper_mock:
   label: Mock plugin
   plugin_manager_service_id: plugin.manager.plugin_test_helper.mock
+  plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator
+  plugin_configuration_schema_id: plugin_test_helper.plugin_configuration.plugin_test_helper_mock.[plugin_id]
diff --git a/modules/plugin_test_helper/src/Plugin/PluginTestHelper/MockManager.php b/modules/plugin_test_helper/src/Plugin/PluginTestHelper/MockManager.php
index 91d8f46..e98aad1 100644
--- a/modules/plugin_test_helper/src/Plugin/PluginTestHelper/MockManager.php
+++ b/modules/plugin_test_helper/src/Plugin/PluginTestHelper/MockManager.php
@@ -15,6 +15,10 @@ use Drupal\plugin\PluginDiscovery\TypedDiscoveryInterface;
 
 /**
  * Provides a plugin manager for testing plugin-related functionality.
+ *
+ * Configuration schemas for this manager's plugins are named
+ * "plugin_test_helper.plugin_configuration.plugin_test_helper_mock.[plugin_id]",
+ * where "[plugin_id]" is the ID of the plugin the schema is for.
  */
 class MockManager extends PluginManagerBase implements TypedDiscoveryInterface {
 
diff --git a/plugin.install b/plugin.install
new file mode 100644
index 0000000..c2271d3
--- /dev/null
+++ b/plugin.install
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains installation, uninstallation, and update functionality.
+ */
+
+use Drupal\plugin\PluginType\ConfigurablePluginTypeInterface;
+
+/**
+ * Updates plugin field default values with plugin configuration schema IDs.
+ */
+function plugin_update_8001(array &$sandbox) {
+  $config_factory = \Drupal::configFactory();
+  /** @var \Drupal\plugin\PluginType\PluginTypeManagerInterface $plugin_type_manager */
+  $plugin_type_manager = \Drupal::service('plugin.plugin_type_manager');
+  $plugin_field_names = $config_factory->listAll('field.field.');
+  foreach ($plugin_field_names as $plugin_field_name) {
+    $config = $config_factory->getEditable($plugin_field_name);
+    if (strpos($config->get('field_type'), 'plugin:') === 0) {
+      $plugin_type_id = explode(':', $config->get('field_type'))[1];
+      $plugin_type = $plugin_type_manager->getPluginType($plugin_type_id);
+      $default_values = $config->get('default_value');
+      foreach ($default_values as &$default_value) {
+        if ($plugin_type instanceof ConfigurablePluginTypeInterface) {
+          $default_value['plugin_configuration_schema_id'] = $plugin_type->getPluginConfigurationSchemaId($default_value['plugin_id']);
+        }
+        else {
+          $default_value['plugin_configuration_schema_id'] = sprintf('plugin.plugin_configuration.%s.%s', $default_value['plugin_type_id'], $default_value['plugin_id']);
+        }
+        unset($default_value['plugin_type_id']);
+        $config->set('default_value', $default_values);
+      }
+      $config->save();
+    }
+  }
+}
diff --git a/plugin.plugin_type.yml b/plugin.plugin_type.yml
index c206d7c..fa9404c 100644
--- a/plugin.plugin_type.yml
+++ b/plugin.plugin_type.yml
@@ -4,16 +4,12 @@ plugin_selector:
   plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator
 
 # Drupal core's plugin types.
-block:
-  label: Block
-  provider: block
-  plugin_manager_service_id: plugin.manager.block
-  plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\BlockPluginDefinitionDecorator
 entity_reference_selector:
   label: Entity reference selector
   provider: core
   plugin_manager_service_id: plugin.manager.entity_reference_selection
   plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator
+  plugin_configuration_schema_id: entity_reference_selection.[plugin_id]
 field_type:
   label: Field type/item
   provider: core
@@ -103,6 +99,14 @@ aggregator_processor:
   plugin_manager_service_id: plugin.manager.aggregator.processor
   plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator
 
+# Block's plugin types.
+block:
+  label: Block
+  provider: block
+  plugin_manager_service_id: plugin.manager.block
+  plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\BlockPluginDefinitionDecorator
+  plugin_configuration_schema_id: block.settings.[plugin_id]
+
 # CKEditor's plugin types.
 ckeditor_plugin:
   label: CKEditor plugin
@@ -151,6 +155,7 @@ migrate_source:
   provider: migrate
   plugin_manager_service_id: plugin.manager.migrate.source
   plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator
+  plugin_configuration_schema_id: migrate.source.[plugin_id]
 migrate_process:
   label: Migration process
   provider: migrate
@@ -161,6 +166,7 @@ migrate_destination:
   provider: migrate
   plugin_manager_service_id: plugin.manager.migrate.destination
   plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator
+  plugin_configuration_schema_id: migrate.destination.[plugin_id]
 migrate_id_map:
   label: Migration ID map
   provider: migrate
@@ -213,11 +219,13 @@ views_area:
   provider: views
   plugin_manager_service_id: plugin.manager.views.area
   plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator
+  plugin_configuration_schema_id: views.area.[plugin_id]
 views_argument:
   label: Views argument
   provider: views
   plugin_manager_service_id: plugin.manager.views.argument
   plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator
+  plugin_configuration_schema_id: views.argument.[plugin_id]
 views_argument_default:
   label: Views argument default
   provider: views
@@ -253,11 +261,13 @@ views_field:
   provider: views
   plugin_manager_service_id: plugin.manager.views.field
   plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator
+  plugin_configuration_schema_id: views.field.[plugin_id]
 views_filter:
   label: Views filter
   provider: views
   plugin_manager_service_id: plugin.manager.views.filter
   plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator
+  plugin_configuration_schema_id: views.filter.[plugin_id]
 views_join:
   label: Views join
   provider: views
@@ -278,6 +288,7 @@ views_relationship:
   provider: views
   plugin_manager_service_id: plugin.manager.views.relationship
   plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator
+  plugin_configuration_schema_id: views.relationship.[plugin_id]
 views_row:
   label: Views row
   provider: views
@@ -288,6 +299,7 @@ views_sort:
   provider: views
   plugin_manager_service_id: plugin.manager.views.sort
   plugin_definition_decorator_class: \Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator
+  plugin_configuration_schema_id: views.sort.[plugin_id]
 views_style:
   label: Views style
   provider: views
diff --git a/src/Plugin/Field/FieldType/PluginCollectionItemBase.php b/src/Plugin/Field/FieldType/PluginCollectionItemBase.php
index 779853a..c12160d 100644
--- a/src/Plugin/Field/FieldType/PluginCollectionItemBase.php
+++ b/src/Plugin/Field/FieldType/PluginCollectionItemBase.php
@@ -12,9 +12,8 @@ use Drupal\Component\Plugin\PluginInspectionInterface;
 use Drupal\Core\Field\FieldItemBase;
 use Drupal\Core\Field\FieldStorageDefinitionInterface;
 use Drupal\Core\TypedData\DataDefinition;
-use Drupal\Core\TypedData\DataDefinitionInterface;
 use Drupal\Core\TypedData\MapDataDefinition;
-use Drupal\Core\TypedData\TypedDataInterface;
+use Drupal\plugin\PluginType\ConfigurablePluginTypeInterface;
 
 /**
  * Provides a base for plugin collection field items.
@@ -24,14 +23,6 @@ abstract class PluginCollectionItemBase extends FieldItemBase implements PluginC
   /**
    * {@inheritdoc}
    */
-  public function __construct(DataDefinitionInterface $definition, $name = NULL, TypedDataInterface $parent = NULL) {
-    parent::__construct($definition, $name, $parent);
-    $this->get('plugin_type_id')->setValue($this->getPluginType()->getId());
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   public function __get($name) {
     // @todo Remove this override once https://www.drupal.org/node/2413471 has
     //   been fixed.
@@ -129,9 +120,6 @@ abstract class PluginCollectionItemBase extends FieldItemBase implements PluginC
    * {@inheritdoc}
    */
   public static function propertyDefinitions(FieldStorageDefinitionInterface $field_definition) {
-    $properties['plugin_type_id'] = DataDefinition::create('string')
-      ->setLabel(t('Plugin type ID'))
-      ->setReadOnly(TRUE);
     $properties['plugin_id'] = DataDefinition::create('plugin_id')
       ->setLabel(t('Plugin ID'));
     $properties['plugin_configuration'] = MapDataDefinition::create('plugin_configuration')
@@ -191,10 +179,11 @@ abstract class PluginCollectionItemBase extends FieldItemBase implements PluginC
     // but we can only represent this item's value using the plugin instance's
     // ID and configuration. parent::getValue() skips computed properties, so we
     // must return them here.
+    $plugin_type = $this->getPluginType();
     return [
-      'plugin_type_id' => $this->get('plugin_type_id')->getValue(),
       'plugin_id' => $this->get('plugin_id')->getValue(),
       'plugin_configuration' => $this->get('plugin_configuration')->getValue(),
+      'plugin_configuration_schema_id' => $plugin_type instanceof ConfigurablePluginTypeInterface ? $plugin_type->getPluginConfigurationSchemaId($this->get('plugin_id')->getValue()) : 'plugin.plugin_configuration.*.*',
     ];
   }
 
diff --git a/src/PluginType/ConfigurablePluginTypeInterface.php b/src/PluginType/ConfigurablePluginTypeInterface.php
new file mode 100644
index 0000000..20718ed
--- /dev/null
+++ b/src/PluginType/ConfigurablePluginTypeInterface.php
@@ -0,0 +1,25 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\plugin\PluginType\ConfigurablePluginTypeInterface.
+ */
+
+namespace Drupal\plugin\PluginType;
+
+/**
+ * Defines a plugin type of which the plugins are configurable.
+ */
+interface ConfigurablePluginTypeInterface extends PluginTypeInterface {
+
+  /**
+   * Gets the ID of the configuration schema for a plugin ID.
+   *
+   * @param string $plugin_id
+   *   The ID of the plugin for whose configuration to get the schema ID.
+   *
+   * @return string
+   */
+  public function getPluginConfigurationSchemaId($plugin_id);
+
+}
diff --git a/src/PluginType/PluginType.php b/src/PluginType/PluginType.php
index caf753c..f290e54 100644
--- a/src/PluginType/PluginType.php
+++ b/src/PluginType/PluginType.php
@@ -8,6 +8,7 @@
 namespace Drupal\plugin\PluginType;
 
 use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\DependencyInjection\ClassResolverInterface;
 use Drupal\Core\DependencyInjection\DependencySerializationTrait;
 use Drupal\Core\StringTranslation\TranslatableMarkup;
@@ -19,11 +20,23 @@ use Symfony\Component\DependencyInjection\ContainerInterface;
 /**
  * Provides a plugin type.
  */
-class PluginType implements PluginTypeInterface {
+class PluginType implements ConfigurablePluginTypeInterface {
 
   use DependencySerializationTrait;
 
   /**
+   * The ID of the configuration schema of plugins of this type.
+   *
+   * @var string
+   *   A configuration schema ID. It may contain the tokens "[plugin_type_id|
+   *   and "[plugin_id]", which will be replaced by the plugin type ID and
+   *   plugin ID respectively.
+   *
+   * @see self::getPluginConfigurationSchemaId()
+   */
+  protected $configurationSchemaId = 'plugin.plugin_configuration.[plugin_type_id].[plugin_id]';
+
+  /**
    * The ID.
    *
    * @var string
@@ -97,10 +110,12 @@ class PluginType implements PluginTypeInterface {
    *   The class resolver.
    * @param \Drupal\Component\Plugin\PluginManagerInterface $plugin_manager
    *   The plugin type's plugin manager.
+   * @param \Drupal\Core\Config\TypedConfigManagerInterface $typed_config_manager
+   *   The typed configuration manager.
    *
    * @param mixed[] $definition
    */
-  public function __construct(array $definition, TranslationInterface $string_translation, ClassResolverInterface $class_resolver, PluginManagerInterface $plugin_manager) {
+  public function __construct(array $definition, TranslationInterface $string_translation, ClassResolverInterface $class_resolver, PluginManagerInterface $plugin_manager, TypedConfigManagerInterface $typed_config_manager) {
     if (!is_string($definition['id']) || !strlen($definition['id'])) {
       throw new \InvalidArgumentException(sprintf('The plugin type definition ID must be a non-empty string, but %s was given.', gettype($definition['id'])));
     }
@@ -121,6 +136,16 @@ class PluginType implements PluginTypeInterface {
       }
       $this->pluginDefinitionDecoratorClass = $definition['plugin_definition_decorator_class'];
     }
+    if (isset($definition['plugin_configuration_schema_id'])) {
+      if (!is_string($definition['plugin_configuration_schema_id'])) {
+        throw new \InvalidArgumentException(sprintf('The plugin type definition "plugin_configuration_schema_id" item must be a string, but %s was given.', gettype($definition['field_type'])));
+      }
+      $this->configurationSchemaId = $definition['plugin_configuration_schema_id'];
+    }
+    $plugin_configuration_schema_id = $this->getPluginConfigurationSchemaId('*');
+    if (!$typed_config_manager->hasConfigSchema($plugin_configuration_schema_id)) {
+      throw new \InvalidArgumentException(sprintf('The plugin type definition "plugin_configuration_schema_id" item references the configuration schema "%s" ("%s"), which does not exist.', $plugin_configuration_schema_id, $this->configurationSchemaId));
+    }
     $operations_provider_class = array_key_exists('operations_provider_class', $definition) ? $definition['operations_provider_class'] : DefaultPluginTypeOperationsProvider::class;
     $this->operationsProvider = $class_resolver->getInstanceFromDefinition($operations_provider_class);
     $this->pluginManager = $plugin_manager;
@@ -131,7 +156,7 @@ class PluginType implements PluginTypeInterface {
    * {@inheritdoc}
    */
   public static function createFromDefinition(ContainerInterface $container, array $definition) {
-    return new static($definition, $container->get('string_translation'), $container->get('class_resolver'), $container->get($definition['plugin_manager_service_id']));
+    return new static($definition, $container->get('string_translation'), $container->get('class_resolver'), $container->get($definition['plugin_manager_service_id']), $container->get('config.typed'));
   }
 
   /**
@@ -199,4 +224,11 @@ class PluginType implements PluginTypeInterface {
     return $this->fieldType;
   }
 
+  /**
+   * {@inheritdoc}
+   */
+  public function getPluginConfigurationSchemaId($plugin_id) {
+    return str_replace(['[plugin_type_id]', '[plugin_id]'], [$this->id, $plugin_id], $this->configurationSchemaId);
+  }
+
 }
diff --git a/src/Tests/HookUpdateNTest.php b/src/Tests/HookUpdateNTest.php
new file mode 100644
index 0000000..5c8f44b
--- /dev/null
+++ b/src/Tests/HookUpdateNTest.php
@@ -0,0 +1,72 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\plugin\Tests\HookUpdateTest.
+ */
+
+namespace Drupal\plugin\Tests;
+
+use Drupal\system\Tests\Update\UpdatePathTestBase;
+
+/**
+ * Tests hook_update_N() implementations.
+ *
+ * @group Plugin
+ */
+class HookUpdateNTest extends UpdatePathTestBase {
+
+  /**
+   * The config factory.
+   *
+   * @var \Drupal\Core\Config\ConfigFactoryInterface
+   */
+  protected $configFactory;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->configFactory = $this->container->get('config.factory');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setDatabaseDumpFiles() {
+    $this->databaseDumpFiles = [
+      realpath(DRUPAL_ROOT . '/core/modules/system/tests/fixtures/update/drupal-8.bare.standard.php.gz'),
+      realpath(__DIR__ . '/../../tests/fixtures/module_installation/database_dump.php'),
+      realpath(__DIR__ . '/../../tests/fixtures/plugin_update_8001/database_dump.php'),
+    ];
+  }
+
+  /**
+   * Tests plugin_update_8001().
+   *
+   * @see plugin_update_8001()
+   */
+  public function testPluginUpdate8001() {
+    $this->runUpdates();
+
+    // Test the ingegrity of the plugin selector fields.
+    /** @var string[] $fields Keys are config names, and values are matching plugin configuration schema IDs */
+    $fields = [
+      'field.field.user.user.field_plugin_selector' => 'plugin.plugin_configuration.plugin_selector.plugin_select_list',
+      'field.field.user.user.field_plugin_test_helper_mock' => 'plugin_test_helper.plugin_configuration.plugin_test_helper_mock.plugin_test_helper_configurable_plugin',
+    ];
+    foreach ($fields as $config_name => $plugin_config_schema_id) {
+      $config = $this->configFactory->get($config_name)->get();
+      foreach ($config['default_value'] as $default_value) {
+        // Confirm that the "plugin_type_id" property has been removed.
+        $this->assertFalse(array_key_exists('plugin_type_id', $default_value));
+        // Confirm that the "plugin_configuration_schema_id" property exists and
+        // has been populated.
+        $this->assertTrue(array_key_exists('plugin_configuration_schema_id', $default_value));
+        $this->assertEqual($default_value['plugin_configuration_schema_id'], $plugin_config_schema_id);
+      }
+    }
+  }
+
+}
diff --git a/src/Tests/Plugin/Field/FieldWidget/PluginSelectorTest.php b/src/Tests/Plugin/Field/FieldWidget/PluginSelectorTest.php
index 81a7d0b..f749a22 100644
--- a/src/Tests/Plugin/Field/FieldWidget/PluginSelectorTest.php
+++ b/src/Tests/Plugin/Field/FieldWidget/PluginSelectorTest.php
@@ -54,7 +54,6 @@ class PluginSelectorTest extends WebTestBase {
     /** @var \Drupal\field\FieldConfigInterface $field */
     $field = FieldConfig::load($field_id);
     $this->assertNotNull($field);
-    $this->assertEqual($field->getDefaultValueLiteral()[0]['plugin_type_id'], $selectable_plugin_type_id);
     $this->assertEqual($field->getDefaultValueLiteral()[0]['plugin_id'], $default_selected_plugin_id);
     $this->assertTrue(is_array($field->getDefaultValueLiteral()[0]['plugin_configuration']));
 
@@ -68,7 +67,6 @@ class PluginSelectorTest extends WebTestBase {
     // Test whether the widget displays field values.
     /** @var \Drupal\Core\Entity\ContentEntityInterface $user */
     $user = entity_load_unchanged('user', $user->id());
-    $this->assertEqual($user->get('field_' . $field_name)->get(0)->get('plugin_type_id')->getValue(), $selectable_plugin_type_id);
     $this->assertEqual($user->get('field_' . $field_name)->get(0)->get('plugin_id')->getValue(), $entity_selected_plugin_id);
   }
 }
diff --git a/tests/fixtures/module_installation/config/schema/plugin.schema.yml b/tests/fixtures/module_installation/config/schema/plugin.schema.yml
new file mode 100644
index 0000000..5dbea13
--- /dev/null
+++ b/tests/fixtures/module_installation/config/schema/plugin.schema.yml
@@ -0,0 +1,43 @@
+plugin.plugin_configuration.plugin_selector.plugin_selector_base:
+  type: mapping
+  mapping:
+    collect_plugin_configuration:
+      label: Collect plugin configuration
+      type: boolean
+    keep_previously_selected_plugins:
+      label: Keep previously selected plugins
+      type: boolean
+    label:
+      label: Label
+      type: label
+    description:
+      label: Label
+      type: label
+    required:
+      label: Required
+      type: boolean
+
+plugin.plugin_configuration.plugin_selector.plugin_radios:
+  type: plugin.plugin_configuration.plugin_selector.plugin_selector_base
+
+plugin.plugin_configuration.plugin_selector.plugin_select_list:
+  type: plugin.plugin_configuration.plugin_selector.plugin_selector_base
+
+# Fallback plugin configuration in case no more specific schemas are available.
+plugin.plugin_configuration.*:
+  type: ignore
+
+# The "plugin" field type schema.
+"field.value.plugin:*":
+  label: Plugin collection field value
+  mapping:
+    plugin_type_id:
+      label: Plugin type ID
+      type: string
+    plugin_id:
+      label: Plugin ID
+      type: string
+    plugin_configuration:
+      label: Plugin configuration
+      type: plugin.plugin_configuration.[%parent.plugin_type_id].[%parent.plugin_id]:
+  type: config_object
diff --git a/tests/fixtures/module_installation/database_dump.php b/tests/fixtures/module_installation/database_dump.php
new file mode 100644
index 0000000..48c4b33
--- /dev/null
+++ b/tests/fixtures/module_installation/database_dump.php
@@ -0,0 +1,45 @@
+<?php
+
+/**
+ * @file
+ * Contains a database dump to mimic a Plugin installation.
+ */
+
+use Drupal\Core\Database\Database;
+use Symfony\Component\Yaml\Yaml;
+
+$connection = Database::getConnection();
+
+// Set the schema version.
+$connection->insert('key_value')
+  ->fields([
+    'collection' => 'system.schema',
+    'name' => 'plugin',
+    'value' => 'i:8000;',
+  ])
+  ->execute();
+$connection->insert('key_value')
+  ->fields([
+    'collection' => 'system.schema',
+    'name' => 'plugin_test_helper',
+    'value' => 'i:8000;',
+  ])
+  ->execute();
+
+// Update core.extension.
+$extensions = $connection->select('config')
+  ->fields('config', ['data'])
+  ->condition('collection', '')
+  ->condition('name', 'core.extension')
+  ->execute()
+  ->fetchField();
+$extensions = unserialize($extensions);
+$extensions['module']['plugin'] = 8000;
+$extensions['module']['plugin_test_helper'] = 8000;
+$connection->update('config')
+  ->fields([
+    'data' => serialize($extensions),
+  ])
+  ->condition('collection', '')
+  ->condition('name', 'core.extension')
+  ->execute();
diff --git a/tests/fixtures/plugin_update_8001/config/field.field.user.user.field_plugin_selector.yml b/tests/fixtures/plugin_update_8001/config/field.field.user.user.field_plugin_selector.yml
new file mode 100644
index 0000000..d9acb02
--- /dev/null
+++ b/tests/fixtures/plugin_update_8001/config/field.field.user.user.field_plugin_selector.yml
@@ -0,0 +1,30 @@
+uuid: 7ce5d8e1-9909-490d-9d01-1d3e5035a6a5
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.user.field_plugin_selector
+  module:
+    - plugin
+    - user
+id: user.user.field_plugin_selector
+field_name: field_plugin_selector
+entity_type: user
+bundle: user
+label: 'Plugin selector'
+description: ''
+required: false
+translatable: false
+default_value:
+  -
+    plugin_type_id: plugin_selector
+    plugin_id: plugin_select_list
+    plugin_configuration:
+      description: null
+      label: null
+      required: false
+      collect_plugin_configuration: true
+      keep_previously_selected_plugins: true
+default_value_callback: ''
+settings: {  }
+field_type: 'plugin:plugin_selector'
diff --git a/tests/fixtures/plugin_update_8001/config/field.field.user.user.field_plugin_test_helper_mock.yml b/tests/fixtures/plugin_update_8001/config/field.field.user.user.field_plugin_test_helper_mock.yml
new file mode 100644
index 0000000..71590ba
--- /dev/null
+++ b/tests/fixtures/plugin_update_8001/config/field.field.user.user.field_plugin_test_helper_mock.yml
@@ -0,0 +1,26 @@
+uuid: 8f056b03-c49e-4912-9500-e8e6c91e0358
+langcode: en
+status: true
+dependencies:
+  config:
+    - field.storage.user.field_plugin_test_helper_mock
+  module:
+    - plugin
+    - user
+id: user.user.field_plugin_test_helper_mock
+field_name: field_plugin_test_helper_mock
+entity_type: user
+bundle: user
+label: 'Mock plugin'
+description: ''
+required: false
+translatable: false
+default_value:
+  -
+    plugin_type_id: plugin_test_helper_mock
+    plugin_id: plugin_test_helper_configurable_plugin
+    plugin_configuration:
+      foo: bar
+default_value_callback: ''
+settings: {  }
+field_type: 'plugin:plugin_test_helper_mock'
diff --git a/tests/fixtures/plugin_update_8001/config/field.storage.user.field_plugin_selector.yml b/tests/fixtures/plugin_update_8001/config/field.storage.user.field_plugin_selector.yml
new file mode 100644
index 0000000..4923cdd
--- /dev/null
+++ b/tests/fixtures/plugin_update_8001/config/field.storage.user.field_plugin_selector.yml
@@ -0,0 +1,19 @@
+uuid: 798b1d11-8c50-43f7-9b1d-291ebc218eb5
+langcode: en
+status: true
+dependencies:
+  module:
+    - plugin
+    - user
+id: user.field_plugin_selector
+field_name: field_plugin_selector
+entity_type: user
+type: 'plugin:plugin_selector'
+settings: {  }
+module: plugin
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/tests/fixtures/plugin_update_8001/config/field.storage.user.field_plugin_test_helper_mock.yml b/tests/fixtures/plugin_update_8001/config/field.storage.user.field_plugin_test_helper_mock.yml
new file mode 100644
index 0000000..ff0840e
--- /dev/null
+++ b/tests/fixtures/plugin_update_8001/config/field.storage.user.field_plugin_test_helper_mock.yml
@@ -0,0 +1,19 @@
+uuid: 68f6406e-960d-4aa1-b503-3d2b3a890d16
+langcode: en
+status: true
+dependencies:
+  module:
+    - plugin
+    - user
+id: user.field_plugin_test_helper_mock
+field_name: field_plugin_test_helper_mock
+entity_type: user
+type: 'plugin:plugin_test_helper_mock'
+settings: {  }
+module: plugin
+locked: false
+cardinality: 1
+translatable: true
+indexes: {  }
+persist_with_no_fields: false
+custom_storage: false
diff --git a/tests/fixtures/plugin_update_8001/database_dump.php b/tests/fixtures/plugin_update_8001/database_dump.php
new file mode 100644
index 0000000..f0793fe
--- /dev/null
+++ b/tests/fixtures/plugin_update_8001/database_dump.php
@@ -0,0 +1,238 @@
+<?php
+
+/**
+ * @file
+ * Updates the database with fixtures to test plugin_update_8001().
+ */
+
+use Drupal\Core\Database\Database;
+use Drupal\Component\Serialization\Yaml;
+use Drupal\field\Entity\FieldStorageConfig;
+
+$connection = Database::getConnection();
+
+// Create the "Plugin selector" plugin field storage.
+$config = Yaml::decode(file_get_contents(__DIR__ . '/config/field.storage.user.field_plugin_selector.yml'));
+$connection->insert('config')
+  ->fields([
+    'collection',
+    'name',
+    'data',
+  ])
+  ->values([
+    'collection' => '',
+    'name' => 'field.storage.' . $config['id'],
+    'data' => serialize($config),
+  ])
+  ->execute();
+// We need to Update the registry of "last installed" field definitions.
+$installed = $connection->select('key_value')
+  ->fields('key_value', ['value'])
+  ->condition('collection', 'entity.definitions.installed')
+  ->condition('name', 'user.field_storage_definitions')
+  ->execute()
+  ->fetchField();
+$installed = unserialize($installed);
+$installed['field_plugin_selector'] = new FieldStorageConfig($config);
+$connection->update('key_value')
+  ->condition('collection', 'entity.definitions.installed')
+  ->condition('name', 'user.field_storage_definitions')
+  ->fields([
+    'value' => serialize($installed)
+  ])
+  ->execute();
+
+// Create the "Plugin selector" plugin field.
+$config = Yaml::decode(file_get_contents(__DIR__ . '/config/field.field.user.user.field_plugin_selector.yml'));
+$connection->insert('config')
+  ->fields([
+    'collection',
+    'name',
+    'data',
+  ])
+  ->values([
+    'collection' => '',
+    'name' => 'field.field.' . $config['id'],
+    'data' => serialize($config),
+  ])
+  ->execute();
+
+// Create the table for the "Plugin selector" plugin field's values.
+$connection->schema()->createTable('user__field_plugin_selector', array(
+  'fields' => array(
+    'bundle' => array(
+      'type' => 'varchar_ascii',
+      'not null' => TRUE,
+      'length' => '128',
+      'default' => '',
+    ),
+    'deleted' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'tiny',
+      'default' => '0',
+    ),
+    'entity_id' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'unsigned' => TRUE,
+    ),
+    'revision_id' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'unsigned' => TRUE,
+    ),
+    'langcode' => array(
+      'type' => 'varchar_ascii',
+      'not null' => TRUE,
+      'length' => '32',
+      'default' => '',
+    ),
+    'delta' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'unsigned' => TRUE,
+    ),
+    'field_plugin_selector_plugin_id' => array(
+      'type' => 'varchar',
+      'not null' => FALSE,
+      'length' => '255',
+    ),
+    'field_plugin_selector_plugin_configuration' => array(
+      'type' => 'blob',
+      'not null' => FALSE,
+      'size' => 'normal',
+    ),
+  ),
+  'primary key' => array(
+    'entity_id',
+    'deleted',
+    'delta',
+    'langcode',
+  ),
+  'indexes' => array(
+    'bundle' => array(
+      'bundle',
+    ),
+    'revision_id' => array(
+      'revision_id',
+    ),
+  ),
+  'mysql_character_set' => 'utf8mb4',
+));
+
+// Create the "Mock plugin" plugin field storage.
+$config = Yaml::decode(file_get_contents(__DIR__ . '/config/field.storage.user.field_plugin_test_helper_mock.yml'));
+$connection->insert('config')
+  ->fields([
+    'collection',
+    'name',
+    'data',
+  ])
+  ->values([
+    'collection' => '',
+    'name' => 'field.storage.' . $config['id'],
+    'data' => serialize($config),
+  ])
+  ->execute();
+// We need to Update the registry of "last installed" field definitions.
+$installed = $connection->select('key_value')
+  ->fields('key_value', ['value'])
+  ->condition('collection', 'entity.definitions.installed')
+  ->condition('name', 'user.field_storage_definitions')
+  ->execute()
+  ->fetchField();
+$installed = unserialize($installed);
+$installed['field_plugin_test_helper_mock'] = new FieldStorageConfig($config);
+$connection->update('key_value')
+  ->condition('collection', 'entity.definitions.installed')
+  ->condition('name', 'user.field_storage_definitions')
+  ->fields([
+    'value' => serialize($installed)
+  ])
+  ->execute();
+
+// Create the "Mock plugin" plugin field.
+$config = Yaml::decode(file_get_contents(__DIR__ . '/config/field.field.user.user.field_plugin_test_helper_mock.yml'));
+$connection->insert('config')
+  ->fields([
+    'collection',
+    'name',
+    'data',
+  ])
+  ->values([
+    'collection' => '',
+    'name' => 'field.field.' . $config['id'],
+    'data' => serialize($config),
+  ])
+  ->execute();
+
+// Create the table for the "Mock plugin" plugin field's values.
+$connection->schema()->createTable('user__field_plugin_test_helper_mock', array(
+  'fields' => array(
+    'bundle' => array(
+      'type' => 'varchar_ascii',
+      'not null' => TRUE,
+      'length' => '128',
+      'default' => '',
+    ),
+    'deleted' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'tiny',
+      'default' => '0',
+    ),
+    'entity_id' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'unsigned' => TRUE,
+    ),
+    'revision_id' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'unsigned' => TRUE,
+    ),
+    'langcode' => array(
+      'type' => 'varchar_ascii',
+      'not null' => TRUE,
+      'length' => '32',
+      'default' => '',
+    ),
+    'delta' => array(
+      'type' => 'int',
+      'not null' => TRUE,
+      'size' => 'normal',
+      'unsigned' => TRUE,
+    ),
+    'field_plugin_test_helper_mock_plugin_id' => array(
+      'type' => 'varchar',
+      'not null' => FALSE,
+      'length' => '255',
+    ),
+    'field_plugin_test_helper_mock_plugin_configuration' => array(
+      'type' => 'blob',
+      'not null' => FALSE,
+      'size' => 'normal',
+    ),
+  ),
+  'primary key' => array(
+    'entity_id',
+    'deleted',
+    'delta',
+    'langcode',
+  ),
+  'indexes' => array(
+    'bundle' => array(
+      'bundle',
+    ),
+    'revision_id' => array(
+      'revision_id',
+    ),
+  ),
+  'mysql_character_set' => 'utf8mb4',
+));
diff --git a/tests/src/Unit/Controller/ListPluginTypesTest.php b/tests/src/Unit/Controller/ListPluginTypesTest.php
index 0db8d7c..084cc91 100644
--- a/tests/src/Unit/Controller/ListPluginTypesTest.php
+++ b/tests/src/Unit/Controller/ListPluginTypesTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\plugin\Unit\Controller;
 
 use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\DependencyInjection\ClassResolverInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\plugin\Controller\ListPluginTypes;
@@ -99,6 +100,11 @@ class ListPluginTypesTest extends UnitTestCase {
 
     $plugin_manager = $this->getMock(PluginManagerInterface::class);
 
+    $typed_config_manager = $this->getMock(TypedConfigManagerInterface::class);
+    $typed_config_manager->expects($this->atLeastOnce())
+      ->method('hasConfigSchema')
+      ->willReturn(TRUE);
+
     $plugin_type_id_a = $this->randomMachineName();
     $plugin_type_label_a = $this->randomMachineName();
     $plugin_type_description_a = $this->randomMachineName();
@@ -108,7 +114,7 @@ class ListPluginTypesTest extends UnitTestCase {
       'description' => $plugin_type_description_a,
       'provider' => $this->randomMachineName(),
     ];
-    $plugin_type_a = new PluginType($plugin_type_definition_a, $this->stringTranslation, $class_resolver, $plugin_manager);
+    $plugin_type_a = new PluginType($plugin_type_definition_a, $this->stringTranslation, $class_resolver, $plugin_manager, $typed_config_manager);
     $plugin_type_id_b = $this->randomMachineName();
     $plugin_type_label_b = $this->randomMachineName();
     $plugin_type_description_b = '';
@@ -118,7 +124,7 @@ class ListPluginTypesTest extends UnitTestCase {
       'description' => $plugin_type_description_b,
       'provider' => $this->randomMachineName(),
     ];
-    $plugin_type_b = new PluginType($plugin_type_definition_b, $this->stringTranslation, $class_resolver, $plugin_manager);
+    $plugin_type_b = new PluginType($plugin_type_definition_b, $this->stringTranslation, $class_resolver, $plugin_manager, $typed_config_manager);
 
     $plugin_types = [
       $plugin_type_id_a => $plugin_type_a,
diff --git a/tests/src/Unit/Controller/ListPluginsTest.php b/tests/src/Unit/Controller/ListPluginsTest.php
index 094dfd1..fd5b1d3 100644
--- a/tests/src/Unit/Controller/ListPluginsTest.php
+++ b/tests/src/Unit/Controller/ListPluginsTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\plugin\Unit\Controller;
 
 use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\DependencyInjection\ClassResolverInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\plugin\Controller\ListPlugins;
@@ -101,6 +102,11 @@ class ListPluginsTest extends UnitTestCase {
 
     $plugin_manager = $this->getMock(PluginManagerInterface::class);
 
+    $typed_config_manager = $this->getMock(TypedConfigManagerInterface::class);
+    $typed_config_manager->expects($this->atLeastOnce())
+      ->method('hasConfigSchema')
+      ->willReturn(TRUE);
+
     $plugin_type_id = $this->randomMachineName();
     $plugin_type_label = $this->randomMachineName();
 
@@ -109,7 +115,7 @@ class ListPluginsTest extends UnitTestCase {
       'label' => $plugin_type_label,
       'provider' => $this->randomMachineName(),
     ];
-    $plugin_type = new PluginType($plugin_type_definition, $this->stringTranslation, $class_resolver, $plugin_manager);
+    $plugin_type = new PluginType($plugin_type_definition, $this->stringTranslation, $class_resolver, $plugin_manager, $typed_config_manager);
 
     $this->pluginTypeManager->expects($this->atLeastOnce())
       ->method('getPluginType')
diff --git a/tests/src/Unit/Plugin/Field/FieldType/PluginCollectionItemDeriverTest.php b/tests/src/Unit/Plugin/Field/FieldType/PluginCollectionItemDeriverTest.php
index 3154a0c..9753bc2 100644
--- a/tests/src/Unit/Plugin/Field/FieldType/PluginCollectionItemDeriverTest.php
+++ b/tests/src/Unit/Plugin/Field/FieldType/PluginCollectionItemDeriverTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\plugin\Unit\Plugin\Field\FieldType;
 
 use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\DependencyInjection\ClassResolverInterface;
 use Drupal\plugin\Plugin\Field\FieldType\PluginCollectionItemDeriver;
 use Drupal\plugin\PluginType\PluginType;
@@ -71,6 +72,11 @@ class PluginCollectionItemDeriverTest extends UnitTestCase {
 
     $plugin_manager = $this->getMock(PluginManagerInterface::class);
 
+    $typed_config_manager = $this->getMock(TypedConfigManagerInterface::class);
+    $typed_config_manager->expects($this->atLeastOnce())
+      ->method('hasConfigSchema')
+      ->willReturn(TRUE);
+
     $provider = $this->randomMachineName();
 
     $plugin_type_id_a = $this->randomMachineName();
@@ -82,7 +88,7 @@ class PluginCollectionItemDeriverTest extends UnitTestCase {
       'description' => $plugin_type_description_a,
       'provider' => $this->randomMachineName(),
     ];
-    $plugin_type_a = new PluginType($plugin_type_definition_a, $string_translation, $class_resolver, $plugin_manager);
+    $plugin_type_a = new PluginType($plugin_type_definition_a, $string_translation, $class_resolver, $plugin_manager, $typed_config_manager);
     $plugin_type_id_b = $this->randomMachineName();
     $plugin_type_label_b = $this->randomMachineName();
     $plugin_type_description_b = '';
@@ -92,7 +98,7 @@ class PluginCollectionItemDeriverTest extends UnitTestCase {
       'description' => $plugin_type_description_b,
       'provider' => $this->randomMachineName(),
     ];
-    $plugin_type_b = new PluginType($plugin_type_definition_b, $string_translation, $class_resolver, $plugin_manager);
+    $plugin_type_b = new PluginType($plugin_type_definition_b, $string_translation, $class_resolver, $plugin_manager, $typed_config_manager);
 
     $plugin_types = [$plugin_type_a, $plugin_type_b];
 
diff --git a/tests/src/Unit/PluginType/PluginTypeTest.php b/tests/src/Unit/PluginType/PluginTypeTest.php
index 1e729ef..2fe775a 100644
--- a/tests/src/Unit/PluginType/PluginTypeTest.php
+++ b/tests/src/Unit/PluginType/PluginTypeTest.php
@@ -8,6 +8,7 @@
 namespace Drupal\Tests\plugin\Unit\PluginType;
 
 use Drupal\Component\Plugin\PluginManagerInterface;
+use Drupal\Core\Config\TypedConfigManagerInterface;
 use Drupal\Core\DependencyInjection\ClassResolverInterface;
 use Drupal\plugin\PluginDefinition\ArrayPluginDefinitionDecorator;
 use Drupal\plugin\PluginDefinition\PluginDefinitionDecoratorInterface;
@@ -63,11 +64,17 @@ class PluginTypeTest extends UnitTestCase {
 
     $class_resolver = $this->getMock(ClassResolverInterface::class);
 
+    $typed_config_manager = $this->getMock(TypedConfigManagerInterface::class);
+    $typed_config_manager->expects($this->atLeastOnce())
+      ->method('hasConfigSchema')
+      ->willReturn(TRUE);
+
     $this->pluginManager = $this->getMock(PluginManagerInterface::class);
 
     $this->container = $this->getMock(ContainerInterface::class);
     $map = [
       ['class_resolver', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $class_resolver],
+      ['config.typed', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $typed_config_manager],
       ['string_translation', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $this->getStringTranslationStub()],
       [$this->pluginTypeDefinition['plugin_manager_service_id'], ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, $this->pluginManager],
     ];
@@ -129,6 +136,27 @@ class PluginTypeTest extends UnitTestCase {
   }
 
   /**
+   * @covers ::getPluginConfigurationSchemaId
+   */
+  public function testGetPluginConfigurationSchemaIdWithDefaultId() {
+    $plugin_id = 'FooBarQux';
+    $expected_schema_id = sprintf('plugin.plugin_configuration.%s.%s', $this->pluginTypeDefinition['id'], $plugin_id);
+    $this->assertSame($expected_schema_id, $this->sut->getPluginConfigurationSchemaId($plugin_id));
+  }
+
+  /**
+   * @covers ::getPluginConfigurationSchemaId
+   */
+  public function testGetPluginConfigurationSchemaIdWithDefinedId() {
+    $plugin_id = 'FooBarQux';
+    $schema_id = 'foo_bar.qux.[plugin_id]';
+    $this->pluginTypeDefinition['plugin_configuration_schema_id'] = $schema_id;
+    $this->sut = PluginType::createFromDefinition($this->container, $this->pluginTypeDefinition);
+    $expected_schema_id = 'foo_bar.qux.' . $plugin_id;
+    $this->assertSame($expected_schema_id, $this->sut->getPluginConfigurationSchemaId($plugin_id));
+  }
+
+  /**
    * @covers ::ensureTypedPluginDefinition
    * @covers ::createFromDefinition
    * @covers ::__construct
