diff --git a/core/lib/Drupal/Core/Config/TypedConfigManager.php b/core/lib/Drupal/Core/Config/TypedConfigManager.php
index ca0eb50..801c3e7 100644
--- a/core/lib/Drupal/Core/Config/TypedConfigManager.php
+++ b/core/lib/Drupal/Core/Config/TypedConfigManager.php
@@ -100,9 +100,13 @@ public function buildDataDefinition(array $definition, $value, $name = NULL, $pa
       // Remove the type from the definition so that it is replaced with the
       // concrete type from schema definitions.
       unset($definition['type']);
+
+      $definition += $this->getDefinitionWithReplacements($type, $replace);
+    }
+    else {
+      // Add default values from type definition.
+      $definition += $this->getDefinition($type);
     }
-    // Add default values from type definition.
-    $definition += $this->getDefinition($type);
 
     $data_definition = $this->createDataDefinition($definition['type']);
 
@@ -116,10 +120,17 @@ public function buildDataDefinition(array $definition, $value, $name = NULL, $pa
   }
 
   /**
-   * {@inheritdoc}
+   * Determines the typed config type for a plugin ID.
+   *
+   * @param string $base_plugin_id
+   *   The plugin ID.
+   * @param array $definitions
+   *   An array of typed config definitions.
+   *
+   * @return string
+   *   The typed config type for the given plugin ID.
    */
-  public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
-    $definitions = $this->getDefinitions();
+  protected function determineType($base_plugin_id, array $definitions) {
     if (isset($definitions[$base_plugin_id])) {
       $type = $base_plugin_id;
     }
@@ -131,13 +142,39 @@ public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
       // If we don't have definition, return the 'undefined' element.
       $type = 'undefined';
     }
+    return $type;
+  }
+
+  /**
+   * Gets a schema definition with replacements for dynamic names.
+   *
+   * @param string $base_plugin_id
+   *   A plugin ID.
+   * @param array $replace
+   *   (optional) An array of replacements for dynamic type names.
+   *
+   * @return array
+   *   A schema definition array.
+   */
+  protected function getDefinitionWithReplacements($base_plugin_id, $replace = []) {
+    $definitions = $this->getDefinitions();
+    $type = $this->determineType($base_plugin_id, $definitions);
     $definition = $definitions[$type];
     // Check whether this type is an extension of another one and compile it.
     if (isset($definition['type'])) {
-      $merge = $this->getDefinition($definition['type'], $exception_on_invalid);
+      $merge = $this->getDefinition($definition['type']);
       // Preserve integer keys on merge, so sequence item types can override
       // parent settings as opposed to adding unused second, third, etc. items.
       $definition = NestedArray::mergeDeepArray(array($merge, $definition), TRUE);
+
+      // Replace dynamic portions of the definition type.
+      if (!empty($replace) && strpos($definition['type'], ']')) {
+        $sub_type = $this->determineType($this->replaceName($definition['type'], $replace), $definitions);
+        // Merge the newly determined subtype definition with the original
+        // definition.
+        $definition = NestedArray::mergeDeepArray([$definitions[$sub_type], $definition], TRUE);
+      }
+
       // Unset type so we try the merge only once per type.
       unset($definition['type']);
       $this->definitions[$type] = $definition;
@@ -153,6 +190,13 @@ public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
   /**
    * {@inheritdoc}
    */
+  public function getDefinition($base_plugin_id, $exception_on_invalid = TRUE) {
+    return $this->getDefinitionWithReplacements($base_plugin_id);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   public function clearCachedDefinitions() {
     $this->schemaStorage->reset();
     parent::clearCachedDefinitions();
diff --git a/core/modules/config/src/Tests/ConfigSchemaTest.php b/core/modules/config/src/Tests/ConfigSchemaTest.php
index 8735db1..6e1e724 100644
--- a/core/modules/config/src/Tests/ConfigSchemaTest.php
+++ b/core/modules/config/src/Tests/ConfigSchemaTest.php
@@ -506,4 +506,72 @@ public function testConfigSchemaInfoAlter() {
     $this->assertEqual($definitions['config_schema_test.hook']['additional_metadata'], 'new schema info');
   }
 
+  /**
+   * Tests saving config when the type is wrapped by a dynamic type.
+   */
+  public function testConfigSaveWithWrappingSchema() {
+    $untyped_values = [
+      'tests' => [
+        [
+          'wrapper_value' => 'foo',
+          'plugin_id' => 'wrapper:foo',
+          'internal_value' => 100,
+        ],
+      ],
+    ];
+    $untyped_to_typed = $untyped_values;
+
+    $typed_values = [
+      'tests' => [
+        [
+          'wrapper_value' => 'foo',
+          'plugin_id' => 'wrapper:foo',
+          'internal_value' => '100',
+        ],
+      ],
+    ];
+
+    // Save config which has a schema that enforces types.
+    \Drupal::configFactory()->getEditable('wrapping.config_schema_test.plugin_types')
+      ->setData($untyped_to_typed)
+      ->save();
+    $this->assertIdentical(\Drupal::config('wrapping.config_schema_test.plugin_types')
+      ->get(), $typed_values);
+  }
+
+  /**
+   * Tests saving config when the type is wrapped by a dynamic type.
+   */
+  public function testConfigSaveWithWrappingSchemaDoubleBrackets() {
+    $untyped_values = [
+      'tests' => [
+        [
+          'wrapper_value' => 'foo',
+          'foo' => 'cat',
+          'bar' => 'dog',
+          'internal_value' => 100,
+        ],
+      ],
+    ];
+    $untyped_to_typed = $untyped_values;
+
+    $typed_values = [
+      'tests' => [
+        [
+          'wrapper_value' => 'foo',
+          'foo' => 'cat',
+          'bar' => 'dog',
+          'internal_value' => '100',
+        ],
+      ],
+    ];
+
+    // Save config which has a schema that enforces types.
+    \Drupal::configFactory()->getEditable('wrapping.config_schema_test.double_brackets')
+      ->setData($untyped_to_typed)
+      ->save();
+    $this->assertIdentical(\Drupal::config('wrapping.config_schema_test.double_brackets')
+      ->get(), $typed_values);
+  }
+
 }
diff --git a/core/modules/config/tests/config_schema_test/config/schema/config_schema_test.schema.yml b/core/modules/config/tests/config_schema_test/config/schema/config_schema_test.schema.yml
index 004ae2b..9a872c6 100644
--- a/core/modules/config/tests/config_schema_test/config/schema/config_schema_test.schema.yml
+++ b/core/modules/config/tests/config_schema_test/config/schema/config_schema_test.schema.yml
@@ -213,3 +213,50 @@ config_test.dynamic.*.third_party.config_schema_test:
       type: integer
     string:
       type: string
+
+wrapping.config_schema_test.plugin_types:
+  type: config_object
+  mapping:
+    tests:
+      type: sequence
+      sequence:
+        - type: wrapping.test.plugin_types.[plugin_id]
+
+wrapping.test.plugin_types.*:
+  type: test.plugin_types.[plugin_id]
+  mapping:
+    wrapper_value:
+      type: string
+
+test.plugin_types.wrapper:*:
+  type: test.plugin_types
+  mapping:
+    internal_value:
+      type: string
+
+wrapping.config_schema_test.double_brackets:
+  type: config_object
+  mapping:
+    tests:
+      type: sequence
+      sequence:
+        - type: wrapping.test.double_brackets.[foo]
+
+wrapping.test.double_brackets.*:
+  type: test.double_brackets.[foo].[bar]
+  mapping:
+    wrapper_value:
+      type: string
+
+test.double_brackets.cat.dog:
+  type: test.double_brackets
+  mapping:
+    internal_value:
+      type: string
+    foo:
+      type: string
+    bar:
+      type: string
+
+test.double_brackets.*:
+  type: mapping
