diff --git a/core/includes/config.inc b/core/includes/config.inc
index c143dd4..6919bfe 100644
--- a/core/includes/config.inc
+++ b/core/includes/config.inc
@@ -1,7 +1,6 @@
 <?php
 
 use Drupal\Core\Config\Config;
-use Drupal\Core\Config\DatabaseStorage;
 use Drupal\Core\Config\FileStorage;
 use Drupal\Core\Config\NullStorage;
 use Drupal\Core\Config\StorageInterface;
@@ -24,7 +23,7 @@ function config_install_default_config($module) {
   $module_config_dir = drupal_get_path('module', $module) . '/config';
   if (is_dir($module_config_dir)) {
     $source_storage = new FileStorage(array('directory' => $module_config_dir));
-    $target_storage = new DatabaseStorage();
+    $target_storage = drupal_container()->get('config.storage');
     $null_storage = new NullStorage();
 
     // Upon module installation, only new config objects need to be created.
@@ -45,8 +44,7 @@ function config_install_default_config($module) {
  * @todo Modules need a way to access the active store, whatever it is.
  */
 function config_get_storage_names_with_prefix($prefix = '') {
-  $storage = new DatabaseStorage();
-  return $storage->listAll($prefix);
+  return drupal_container()->get('config.storage')->listAll($prefix);
 }
 
 /**
@@ -129,17 +127,16 @@ function config_sync_changes(array $config_changes, StorageInterface $source_sto
 }
 
 /**
- * Imports configuration from FileStorage to DatabaseStorage.
+ * Imports configuration from FileStorage to the active store.
  *
  * @return bool|null
  *   TRUE if configuration was imported successfully, FALSE in case of a
  *   synchronization error, or NULL if there are no changes to synchronize.
  */
 function config_import() {
-  // Retrieve a list of differences between FileStorage and DatabaseStorage.
-  // @todo Leverage DI + config.storage.info.
+  // Retrieve a list of differences between FileStorage and the active store.
   $source_storage = new FileStorage();
-  $target_storage = new DatabaseStorage();
+  $target_storage = drupal_container()->get('config.storage');
 
   $config_changes = config_sync_get_changes($source_storage, $target_storage);
   if (empty($config_changes)) {
@@ -183,8 +180,6 @@ function config_import() {
  * @todo Add support for other extension types; e.g., themes etc.
  */
 function config_import_invoke_owner(array $config_changes, StorageInterface $source_storage, StorageInterface $target_storage) {
-  $storage_dispatcher = drupal_container()->get('config.storage.dispatcher');
-
   // Allow modules to take over configuration change operations for
   // higher-level configuration data.
   // First pass deleted, then new, and lastly changed configuration, in order to
@@ -197,13 +192,13 @@ function config_import_invoke_owner(array $config_changes, StorageInterface $sou
       // handle the configuration change.
       $handled_by_module = FALSE;
       if (module_hook($module, 'config_import_' . $op)) {
-        $old_config = new Config($storage_dispatcher);
+        $old_config = new Config($target_storage);
         $old_config
           ->setName($name)
           ->load();
 
         $data = $source_storage->read($name);
-        $new_config = new Config($storage_dispatcher);
+        $new_config = new Config($target_storage);
         $new_config->setName($name);
         if ($data !== FALSE) {
           $new_config->setData($data);
@@ -220,12 +215,11 @@ function config_import_invoke_owner(array $config_changes, StorageInterface $sou
 }
 
 /**
- * Exports configuration from DatabaseStorage to FileStorage.
+ * Exports configuration from the active store to FileStorage.
  */
 function config_export() {
-  // Retrieve a list of differences between DatabaseStorage and FileStorage.
-  // @todo Leverage DI + config.storage.info.
-  $source_storage = new DatabaseStorage();
+  // Retrieve a list of differences between the active store and FileStorage.
+  $source_storage = drupal_container()->get('config.storage');
   $target_storage = new FileStorage();
 
   $config_changes = config_sync_get_changes($source_storage, $target_storage);
diff --git a/core/includes/form.inc b/core/includes/form.inc
index 6a747b6..846e684 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -3533,6 +3533,7 @@ function form_process_machine_name($element, &$form_state) {
   // 'source' only) would leave all other properties undefined, if the defaults
   // were defined in hook_element_info(). Therefore, we apply the defaults here.
   $element['#machine_name'] += array(
+    // @todo Use 'label' by default.
     'source' => array('name'),
     'target' => '#' . $element['#id'],
     'label' => t('Machine name'),
diff --git a/core/includes/module.inc b/core/includes/module.inc
index f40161d..bed5bc9 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -6,7 +6,7 @@
  */
 
 use Drupal\Component\Graph\Graph;
-use Drupal\Core\Config\DatabaseStorage;
+use Drupal\Core\Config\NullStorage;
 
 /**
  * Load all the modules that have been enabled in the system table.
@@ -628,19 +628,26 @@ function module_uninstall($module_list = array(), $uninstall_dependents = TRUE)
     $module_list = array_keys($module_list);
   }
 
-  $storage = new DatabaseStorage();
+  $source_storage = new NullStorage();
+  $target_storage = drupal_container()->get('config.storage');
   foreach ($module_list as $module) {
+    // Remove all configuration belonging to the module.
+    $config_changes = $target_storage->listAll($module . '.');
+    if (!empty($config_changes)) {
+      $config_changes = array(
+        'delete' => $config_changes,
+        'change' => array(),
+        'create' => array(),
+      );
+      $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
+      config_sync_changes($remaining_changes, $source_storage, $target_storage);
+    }
+
     // Uninstall the module.
     module_load_install($module);
     module_invoke($module, 'uninstall');
     drupal_uninstall_schema($module);
 
-    // Remove all configuration belonging to the module.
-    $config_names = $storage->listAll($module . '.');
-    foreach ($config_names as $config_name) {
-      config($config_name)->delete();
-    }
-
     watchdog('system', '%module module uninstalled.', array('%module' => $module), WATCHDOG_INFO);
     drupal_set_installed_schema_version($module, SCHEMA_UNINSTALLED);
   }
diff --git a/core/lib/Drupal/Core/Config/CachedFileStorage.php b/core/lib/Drupal/Core/Config/CachedFileStorage.php
new file mode 100644
index 0000000..7fbaef9
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/CachedFileStorage.php
@@ -0,0 +1,125 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Config\CachedFileStorage.
+ */
+
+namespace Drupal\Core\Config;
+
+use Drupal\Core\Config\FileStorage;
+
+/**
+ * Storage controller that uses FileStorage as canonical storage and
+ * DatabaseStorage as a cache (active store).
+ */
+class CachedFileStorage implements StorageInterface {
+
+  /**
+   * The configuration options for the sub-storage controllers.
+   *
+   * An associative array whose keys are the class names of the storage
+   * controllers to use, and whose values are the respective configuration
+   * options for each storage controller.
+   *
+   * Two array elements are expected. A key and definition for
+   * Drupal\Core\Config\FileStorage is required. The other element should point
+   * to another storage controller that is used as cache.
+   *
+   * @var array
+   */
+  protected $options;
+
+  /**
+   * The instantiated sub-storage controllers.
+   *
+   * @var array
+   */
+  protected $storages = array();
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::__construct().
+   */
+  public function __construct(array $options = array()) {
+    $this->options = $options;
+
+    $this->storages['file'] = new FileStorage($options['Drupal\Core\Config\FileStorage']);
+
+    unset($options['Drupal\Core\Config\FileStorage']);
+    list($active_class, $active_options) = each($options);
+    $this->storages['active'] = new $active_class($active_options);
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::exists().
+   */
+  public function exists($name) {
+    // A single filestat is faster than a complex cache lookup and possibly
+    // subsequent filestat.
+    return $this->storages['file']->exists($name);
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::read().
+   */
+  public function read($name) {
+    // Check the cache.
+    $data = $this->storages['active']->read($name);
+    // If the cache returns no result, check the file storage.
+    if ($data === FALSE) {
+      $data = $this->storages['file']->read($name);
+      // @todo Should the config object be cached if it does not exist?
+      if ($data !== FALSE) {
+        $this->storages['active']->write($name, $data);
+      }
+    }
+    return $data;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::write().
+   */
+  public function write($name, array $data) {
+    $success = $this->storages['file']->write($name, $data);
+    $this->storages['active']->delete($name);
+    return $success;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::delete().
+   */
+  public function delete($name) {
+    $success = TRUE;
+    foreach ($this->storages as $storage) {
+      if (!$storage->delete($name)) {
+        $success = FALSE;
+      }
+    }
+    return $success;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::encode().
+   *
+   * @todo Remove encode() from StorageInterface.
+   */
+  public static function encode($data) {
+    return $this->storages['file']->encode($data);
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::decode().
+   *
+   * @todo Remove decode() from StorageInterface.
+   */
+  public static function decode($raw) {
+    return $this->storages['file']->decode($raw);
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::listAll().
+   */
+  public function listAll($prefix = '') {
+    return $this->storages['file']->listAll($prefix);
+  }
+}
diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php
index a749a4b..3b97b69 100644
--- a/core/lib/Drupal/Core/Config/Config.php
+++ b/core/lib/Drupal/Core/Config/Config.php
@@ -34,21 +34,21 @@ class Config {
   protected $data = array();
 
   /**
-   * The injected storage dispatcher object.
+   * The storage used for reading and writing.
    *
-   * @var Drupal\Core\Config\StorageDispatcher
+   * @var Drupal\Core\Config\StorageInterface
    */
-  protected $storageDispatcher;
+  protected $storage;
 
   /**
    * Constructs a configuration object.
    *
-   * @param Drupal\Core\Config\StorageDispatcher $storageDispatcher
-   *   A storage dispatcher object to use for reading and writing the
+   * @param Drupal\Core\Config\StorageInterface $storage
+   *   A storage controller object to use for reading and writing the
    *   configuration data.
    */
-  public function __construct(StorageDispatcher $storageDispatcher) {
-    $this->storageDispatcher = $storageDispatcher;
+  public function __construct(StorageInterface $storage) {
+    $this->storage = $storage;
   }
 
   /**
@@ -222,7 +222,7 @@ class Config {
    * Loads configuration data into this object.
    */
   public function load() {
-    $data = $this->storageDispatcher->selectStorage('read', $this->name)->read($this->name);
+    $data = $this->storage->read($this->name);
     if ($data === FALSE) {
       $this->isNew = TRUE;
       $this->setData(array());
@@ -239,7 +239,7 @@ class Config {
    */
   public function save() {
     $this->sortByKey($this->data);
-    $this->storageDispatcher->selectStorage('write', $this->name)->write($this->name, $this->data);
+    $this->storage->write($this->name, $this->data);
     $this->isNew = FALSE;
     return $this;
   }
@@ -268,8 +268,9 @@ class Config {
    * Deletes the configuration object.
    */
   public function delete() {
+    // @todo Consider to remove the pruning of data for Config::delete().
     $this->data = array();
-    $this->storageDispatcher->selectStorage('write', $this->name)->delete($this->name);
+    $this->storage->delete($this->name);
     $this->isNew = TRUE;
     return $this;
   }
diff --git a/core/lib/Drupal/Core/Config/ConfigFactory.php b/core/lib/Drupal/Core/Config/ConfigFactory.php
index 1fbca62..5e93c69 100644
--- a/core/lib/Drupal/Core/Config/ConfigFactory.php
+++ b/core/lib/Drupal/Core/Config/ConfigFactory.php
@@ -15,29 +15,28 @@ namespace Drupal\Core\Config;
  *
  * @see Drupal\Core\Config\Config
  *
- * Each configuration object gets a storage dispatcher object injected, which
- * determines the storage controller to use for reading and writing the
- * configuration data.
+ * Each configuration object gets a storage controller object injected, which
+ * is used for reading and writing the configuration data.
  *
- * @see Drupal\Core\Config\StorageDispatcher
+ * @see Drupal\Core\Config\StorageInterface
  */
 class ConfigFactory {
   /**
-   * A storage dispatcher instance to use for reading and writing configuration data.
+   * A storage controller instance to use for reading and writing configuration data.
    *
-   * @var Drupal\Core\Config\StorageDispatcher
+   * @var Drupal\Core\Config\StorageInterface
    */
-  protected $storageDispatcher;
+  protected $storage;
 
   /**
    * Constructs the Config factory.
    *
-   * @param Drupal\Core\Config\StorageDispatcher $storage_dispatcher
-   *   The storage dispatcher object to use for reading and writing
+   * @param Drupal\Core\Config\StorageInterface $storage
+   *   The storage controller object to use for reading and writing
    *   configuration data.
    */
-  public function __construct(StorageDispatcher $storage_dispatcher) {
-    $this->storageDispatcher = $storage_dispatcher;
+  public function __construct(StorageInterface $storage) {
+    $this->storage = $storage;
   }
 
   /**
@@ -68,7 +67,7 @@ class ConfigFactory {
     // @todo The decrease of CPU time is interesting, since that means that
     //   ContainerBuilder involves plenty of function calls (which are known to
     //   be slow in PHP).
-    $config = new Config($this->storageDispatcher);
+    $config = new Config($this->storage);
     return $config->setName($name);
   }
 }
diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php
index f30011c..8ab64f6 100644
--- a/core/lib/Drupal/Core/Config/DatabaseStorage.php
+++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php
@@ -44,6 +44,15 @@ class DatabaseStorage implements StorageInterface {
   }
 
   /**
+   * Implements Drupal\Core\Config\StorageInterface::exists().
+   */
+  public function exists($name) {
+    return (bool) $this->getConnection()->queryRange('SELECT 1 FROM {config} WHERE name = :name', 0, 1, array(
+      ':name' => $name,
+    ), $this->options)->fetchField();
+  }
+
+  /**
    * Implements Drupal\Core\Config\StorageInterface::read().
    *
    * @throws PDOException
@@ -56,8 +65,8 @@ class DatabaseStorage implements StorageInterface {
     // read without actually having the database available. In this case,
     // catch the exception and just return an empty array so the caller can
     // handle it if need be.
-    // @todo Remove this and use appropriate StorageDispatcher configuration in
-    //   the installer instead.
+    // @todo Remove this and use appropriate config.storage service definition
+    //   in the installer instead.
     try {
       $raw = $this->getConnection()->query('SELECT data FROM {config} WHERE name = :name', array(':name' => $name), $this->options)->fetchField();
       if ($raw !== FALSE) {
diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php
index 033555d..727adef 100644
--- a/core/lib/Drupal/Core/Config/FileStorage.php
+++ b/core/lib/Drupal/Core/Config/FileStorage.php
@@ -54,10 +54,7 @@ class FileStorage implements StorageInterface {
   }
 
   /**
-   * Returns whether the configuration file exists.
-   *
-   * @return bool
-   *   TRUE if the configuration file exists, FALSE otherwise.
+   * Implements Drupal\Core\Config\StorageInterface::exists().
    */
   public function exists($name) {
     return file_exists($this->getFilePath($name));
diff --git a/core/lib/Drupal/Core/Config/MultiStorage.php b/core/lib/Drupal/Core/Config/MultiStorage.php
new file mode 100644
index 0000000..484501a
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/MultiStorage.php
@@ -0,0 +1,117 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\Core\Config\MultiStorage.
+ */
+
+namespace Drupal\Core\Config;
+
+/**
+ * Defines a storage controller that wraps multiple others.
+ *
+ * This storage controller has no actual storage attached to itself, but rather
+ * forwards the operations to one or more other storage controllers, depending
+ * on the operation being performed.
+ *
+ * The options define which other storage controllers are used, along with their
+ * respective options.
+ *
+ * Only the first defined storage controller is used for reading configuration.
+ * All writes and deletes are performed on all storage controllers.
+ */
+class MultiStorage implements StorageInterface {
+
+  /**
+   * The configuration options for the sub-storage controllers.
+   *
+   * An associative array whose keys are the class names of the storage
+   * controllers to use, and whose values are the respective configuration
+   * options for each storage controller.
+   *
+   * @var array
+   */
+  protected $options;
+
+  /**
+   * The instantiated sub-storage controllers.
+   *
+   * @var array
+   */
+  protected $storages = array();
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::__construct().
+   */
+  public function __construct(array $options = array()) {
+    foreach ($options as $storage_controller => $storage_options) {
+      $this->storages[] = new $storage_controller($storage_options);
+    }
+    $this->options = $options;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::exists().
+   */
+  public function exists($name) {
+    return $this->storages[0]->exists($name);
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::read().
+   */
+  public function read($name) {
+    return $this->storages[0]->read($name);
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::write().
+   */
+  public function write($name, array $data) {
+    $success = TRUE;
+    foreach ($this->storages as $storage) {
+      if (!$storage->write($name, $data)) {
+        $success = FALSE;
+      }
+    }
+    return $success;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::delete().
+   */
+  public function delete($name) {
+    $success = TRUE;
+    foreach ($this->storages as $storage) {
+      if (!$storage->delete($name)) {
+        $success = FALSE;
+      }
+    }
+    return $success;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::encode().
+   *
+   * @todo Remove encode() from StorageInterface.
+   */
+  public static function encode($data) {
+    return $this->storages[0]->encode($data);
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::decode().
+   *
+   * @todo Remove decode() from StorageInterface.
+   */
+  public static function decode($raw) {
+    return $this->storages[0]->decode($raw);
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::listAll().
+   */
+  public function listAll($prefix = '') {
+    return $this->storages[0]->listAll($prefix);
+  }
+}
diff --git a/core/lib/Drupal/Core/Config/NullStorage.php b/core/lib/Drupal/Core/Config/NullStorage.php
index fede4f0..2e1c06c 100644
--- a/core/lib/Drupal/Core/Config/NullStorage.php
+++ b/core/lib/Drupal/Core/Config/NullStorage.php
@@ -29,6 +29,13 @@ class NullStorage implements StorageInterface {
   }
 
   /**
+   * Implements Drupal\Core\Config\StorageInterface::exists().
+   */
+  public function exists($name) {
+    return FALSE;
+  }
+
+  /**
    * Implements Drupal\Core\Config\StorageInterface::read().
    */
   public function read($name) {
diff --git a/core/lib/Drupal/Core/Config/StorageDispatcher.php b/core/lib/Drupal/Core/Config/StorageDispatcher.php
deleted file mode 100644
index ebf7049..0000000
--- a/core/lib/Drupal/Core/Config/StorageDispatcher.php
+++ /dev/null
@@ -1,105 +0,0 @@
-<?php
-
-namespace Drupal\Core\Config;
-
-/**
- * Dispatches read/write operations to storage controllers.
- *
- * The storage dispatcher determines which storage out of multiple is configured
- * and allowed to handle a particular configuration object, depending on the
- * read/write operation being performed.
- *
- * The information about available storage controllers and their configuration
- * options is passed once into the constructor and normally should not change
- * within a single request or context. Special use-cases, such as import and
- * export operations, should instantiate a custom storage dispatcher tailored
- * to their specific needs.
- *
- * The storage dispatcher instantiates storage controllers on demand, and only
- * once per storage.
- *
- * @see Drupal\Core\Config\StorageInterface
- */
-class StorageDispatcher {
-
-  /**
-   * Information about available storage controllers.
-   *
-   * @var array
-   */
-  protected $storageInfo;
-
-  /**
-   * Instantiated storage controller objects.
-   *
-   * @see Drupal\Core\Config\StorageInterface
-   *
-   * @var array
-   */
-  protected $storageInstances;
-
-  /**
-   * Constructs the storage dispatcher object.
-   *
-   * @param array $storage_info
-   *   An associative array defining the storage controllers to use and any
-   *   required configuration options for them; e.g.:
-   *   @code
-   *   array(
-   *     'Drupal\Core\Config\DatabaseStorage' => array(
-   *       'target' => 'default',
-   *       'read' => TRUE,
-   *       'write' => TRUE,
-   *     ),
-   *     'Drupal\Core\Config\FileStorage' => array(
-   *       'directory' => 'sites/default/files/config',
-   *       'read' => TRUE,
-   *       'write' => FALSE,
-   *     ),
-   *   )
-   *   @endcode
-   */
-  public function __construct(array $storage_info) {
-    $this->storageInfo = $storage_info;
-  }
-
-  /**
-   * Returns a storage controller to use for a given operation.
-   *
-   * Handles the core functionality of the storage dispatcher by determining
-   * which storage can handle a particular storage access operation and
-   * configuration object.
-   *
-   * @param string $access_operation
-   *   The operation access level; either 'read' or 'write'. Use 'write' both
-   *   for saving and deleting configuration.
-   * @param string $name
-   *   The name of the configuration object that is operated on.
-   *
-   * @return Drupal\Core\Config\StorageInterface
-   *   The storage controller instance that can handle the requested operation.
-   *
-   * @throws Drupal\Core\Config\ConfigException
-   *
-   * @todo Allow write operations to write to multiple storages.
-   */
-  public function selectStorage($access_operation, $name) {
-    // Determine the appropriate storage controller to use.
-    // Take the first defined storage that allows $op.
-    foreach ($this->storageInfo as $class => $storage_config) {
-      if (!empty($storage_config[$access_operation])) {
-        $storage_class = $class;
-        break;
-      }
-    }
-    if (!isset($storage_class)) {
-      throw new ConfigException("Failed to find storage controller that allows $access_operation access for $name.");
-    }
-
-    // Instantiate a new storage controller object, if there is none yet.
-    if (!isset($this->storageInstances[$storage_class])) {
-      $this->storageInstances[$storage_class] = new $storage_class($this->storageInfo[$storage_class]);
-    }
-    return $this->storageInstances[$storage_class];
-  }
-}
diff --git a/core/lib/Drupal/Core/Config/StorageInterface.php b/core/lib/Drupal/Core/Config/StorageInterface.php
index 806ee87..4fb9869 100644
--- a/core/lib/Drupal/Core/Config/StorageInterface.php
+++ b/core/lib/Drupal/Core/Config/StorageInterface.php
@@ -25,6 +25,17 @@ interface StorageInterface {
   public function __construct(array $options = array());
 
   /**
+   * Returns whether a configuration object exists.
+   *
+   * @param string $name
+   *   The name of a configuration object to test.
+   *
+   * @return bool
+   *   TRUE if the configuration object exists, FALSE otherwise.
+   */
+  public function exists($name);
+
+  /**
    * Reads configuration data from the storage.
    *
    * @param string $name
diff --git a/core/lib/Drupal/Core/ConfigThingie/ConfigThingieBase.php b/core/lib/Drupal/Core/ConfigThingie/ConfigThingieBase.php
new file mode 100644
index 0000000..3f83cf4
--- /dev/null
+++ b/core/lib/Drupal/Core/ConfigThingie/ConfigThingieBase.php
@@ -0,0 +1,232 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\ConfigThingie\ConfigThingieBase.
+ */
+
+namespace Drupal\Core\ConfigThingie;
+
+use Drupal\Core\Config\Config;
+
+/**
+ * Defines the base configuration thingie.
+ */
+abstract class ConfigThingieBase implements ConfigThingieInterface {
+  /**
+   * The configuration object.
+   *
+   * @var Drupal\Core\Config\Config
+   */
+  protected $config;
+
+  /**
+   * The key of the thingie's ID (machine name).
+   *
+   * @var string
+   */
+  public $idKey = 'id';
+
+  /**
+   * The key of the thingie's label.
+   *
+   * @var string
+   */
+  public $labelKey = 'label';
+
+  /**
+   * The thingie's original ID, if any.
+   *
+   * @var string|null
+   */
+  public $originalID;
+
+  /**
+   * The original configuration object, if any, upon update.
+   *
+   * @var Drupal\Core\ConfigThingie\ConfigThingieInterface
+   */
+  public $original;
+
+  /**
+   * Constructs a new configuration thingie.
+   */
+  public function __construct(Config $config) {
+    $this->setConfig($config);
+  }
+
+  /**
+   * Returns the configuration thingie object's name prefix.
+   *
+   * @return string
+   *   The configuration object name prefix; e.g., for a 'node.type.article'
+   *   thingie, this returns 'node.type'.
+   */
+  abstract public function getConfigPrefix();
+
+  /**
+   * Returns the fully qualified configuration object name for this configuration thingie.
+   *
+   * @return string
+   *   The configuration object name for this configuration thingie. Normally,
+   *   ConfigThingieInterface::getConfigPrefix() and
+   *   ConfigThingieInterface::getID() form the full configuration object name;
+   *   i.e., '[prefix].[id]'.
+   */
+  public function getConfigName() {
+    return $this->getConfigPrefix() . '.' . $this->getID();
+  }
+
+  /**
+   * Returns the basename used for events/hooks for this configuration thingie.
+   *
+   * @return string
+   *   The basename for events/hooks; e.g., for a 'node.type.article' thingie,
+   *   this returns 'node_type'. Normally, this is auto-generated based on
+   *   ConfigThingieInterface::getConfigPrefix().
+   */
+  public function getEventBasename() {
+    return str_replace('.', '_', $this->getConfigPrefix());
+  }
+
+  /**
+   * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::getID().
+   */
+  public function getID() {
+    return $this->config->get($this->idKey);
+  }
+
+  /**
+   * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::isNew().
+   */
+  public function isNew() {
+    return $this->config->isNew();
+  }
+
+  /**
+   * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::getLabel().
+   */
+  public function getLabel() {
+    return $this->config->get($this->labelKey);
+  }
+
+  /**
+   * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::get().
+   */
+  public function get($property_name) {
+    return $this->config->get($property_name);
+  }
+
+  /**
+   * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::set().
+   */
+  public function set($property_name, $value) {
+    return $this->config->set($property_name, $value);
+  }
+
+  /**
+   * Sets the configuration object for this configuration thingie.
+   *
+   * @param Drupal\Core\Config\Config $config
+   *   The configuration object containing the data for this configuration
+   *   thingie.
+   *
+   * @return Drupal\Core\ConfigThingie\ConfigThingieInterface
+   *   This configuration thingie (suitable for chained method calls).
+   */
+  public function setConfig(Config $config) {
+    $this->config = $config;
+    // Only set originalID, if the passed in configuration object is stored
+    // already.
+    if (!$this->config->isNew()) {
+      $this->originalID = $this->getID();
+    }
+
+    // Allow modules to react upon load.
+    module_invoke_all($this->getEventBasename() . '_load', $this);
+
+    return $this;
+  }
+
+  /**
+   * Sets the unchanged original of this configuration thingie.
+   *
+   * @param Drupal\Core\Config\Config $config
+   *   The configuration object containing the original data for this
+   *   configuration thingie.
+   *
+   * @return Drupal\Core\ConfigThingie\ConfigThingieInterface
+   *   This configuration thingie (suitable for chained method calls).
+   */
+  public function setOriginal(Config $config) {
+    $this->original = new $this($config);
+    // Ensure that originalID contains the ID of the supplied original
+    // configuration object. setOriginal() may be called from outside of this
+    // class (e.g., hook_config_import()) in order to set a specific original.
+    $this->originalID = $this->original->getID();
+    return $this;
+  }
+
+  /**
+   * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::save().
+   */
+  public function save() {
+    // Provide the original configuration thingie in $this->original, if any.
+    // originalID is only set, if this configuration thingie already existed
+    // prior to saving.
+    if (isset($this->originalID)) {
+      // Load the original configuration object.
+      // This cannot use ConfigThingieBase::getConfigName(), since that would
+      // yield the name using the current/new ID.
+      $original_config = config($this->getConfigPrefix() . '.' . $this->originalID);
+      // Given the original configuration object, instantiate a new class of the
+      // current class, and provide it in $this->original.
+      $this->setOriginal($original_config);
+    }
+
+    // Ensure that the configuration object name uses the current ID.
+    $this->config->setName($this->getConfigName());
+
+    // Allow modules to react prior to saving.
+    module_invoke_all($this->getEventBasename() . '_presave', $this);
+
+    // Save the configuration object.
+    $this->config->save();
+
+    if (isset($this->originalID)) {
+      // Allow modules to react after inserting new configuration.
+      module_invoke_all($this->getEventBasename() . '_update', $this);
+
+      // Delete the original configuration, if it was renamed.
+      if ($this->originalID !== $this->getID()) {
+        // Configuration data is emptied out upon delete, so back it up and
+        // re-inject it. Delete the old configuration data directly; hooks will
+        // get and will be able to react to the data in $this->original.
+        // @todo Consider to remove the pruning of data for Config::delete().
+        $original_data = $original_config->get();
+        $original_config->delete();
+        $original_config->setData($original_data);
+      }
+    }
+    else {
+      // Allow modules to react after updating existing configuration.
+      module_invoke_all($this->getEventBasename() . '_insert', $this);
+    }
+
+    return $this;
+  }
+
+  /**
+   * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::delete().
+   */
+  public function delete() {
+    // Allow modules to react prior to deleting the configuration.
+    module_invoke_all($this->getEventBasename() . '_predelete', $this);
+
+    // Delete the configuration object.
+    $this->config->delete();
+
+    // Allow modules to react after deleting the configuration.
+    module_invoke_all($this->getEventBasename() . '_delete', $this);
+  }
+}
diff --git a/core/lib/Drupal/Core/ConfigThingie/ConfigThingieInterface.php b/core/lib/Drupal/Core/ConfigThingie/ConfigThingieInterface.php
new file mode 100644
index 0000000..506ffa2
--- /dev/null
+++ b/core/lib/Drupal/Core/ConfigThingie/ConfigThingieInterface.php
@@ -0,0 +1,91 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\ConfigThingie\ConfigThingieInterface.
+ */
+
+namespace Drupal\Core\ConfigThingie;
+
+/**
+ * Defines the interface common for all configuration thingies.
+ */
+interface ConfigThingieInterface {
+  /**
+   * Returns the ID (machine name) of this configuration thingie.
+   *
+   * @return string
+   *   The ID (machine name) of this configuration thingie. Normally, this is
+   *   the part of the configuration object name after
+   *   ConfigThingieInterface::getConfigPrefix(), and also a property within the
+   *   configuration object.
+   */
+  public function getID();
+
+  /**
+   * Returns whether this configuration thingie is new or have been saved already.
+   *
+   * @return bool
+   *   Whether this configuration thingie is new (TRUE) or has been saved
+   *   already (FALSE).
+   */
+  public function isNew();
+
+  /**
+   * Returns the human-readable label of this configuration thingie.
+   *
+   * @return string
+   *   The label of this configuration thingie.
+   */
+  public function getLabel();
+
+  /**
+   * Returns the router path to the administrative page for this configuration thingie.
+   *
+   * @return string
+   *   The router path of this configuration thingie.
+   *
+   * @todo Check whether this makes sense. Requires access handling and stuff,
+   *   which should probably be harmonized for all configuration thingies, too,
+   *   but... later.
+   */
+  public function getURI();
+
+  /**
+   * Returns the value of a given property from this configuration thingie.
+   *
+   * @param string $property_name
+   *   The name of the property to look up.
+   *
+   * @return mixed|null
+   *   The value of $property_name in the configuration object, or NULL if the
+   *   property is not defined.
+   */
+  public function get($property_name);
+
+  /**
+   * Sets the value of a given property for this configuration thingie.
+   *
+   * @param string $property_name
+   *   The name of the property to set.
+   * @param mixed $value
+   *   The value to set.
+   *
+   * @return Drupal\Core\ConfigThingie\ConfigThingieInterface
+   *   This configuration thingie (suitable for chained method calls).
+   */
+  public function set($property_name, $value);
+
+  /**
+   * Saves this configuration thingie.
+   *
+   * @return Drupal\Core\ConfigThingie\ConfigThingieInterface
+   *   This configuration thingie (suitable for chained method calls).
+   */
+  public function save();
+
+  /**
+   * Deletes this configuration thingie.
+   */
+  public function delete();
+}
diff --git a/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php
index a82f5b6..d830708 100644
--- a/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php
+++ b/core/lib/Drupal/Core/DependencyInjection/ContainerBuilder.php
@@ -43,26 +43,28 @@ class ContainerBuilder extends BaseContainerBuilder {
     // Register the default language content.
     $this->register(LANGUAGE_TYPE_CONTENT, 'Drupal\\Core\\Language\\Language');
 
-    // Register configuration storage dispatcher.
-    $this->setParameter('config.storage.info', array(
+    // Register configuration storage class and options.
+    // @todo The active store and its options need to be configurable.
+    //   Use either global $conf (recursion warning) or global $config, or a
+    //   bootstrap configuration *file* to allow to set/override this very
+    //   lowest of low level configuration.
+    $this->setParameter('config.storage.options', array(
       'Drupal\Core\Config\DatabaseStorage' => array(
         'connection' => 'default',
         'target' => 'default',
-        'read' => TRUE,
-        'write' => TRUE,
       ),
       'Drupal\Core\Config\FileStorage' => array(
         'directory' => config_get_config_directory(),
-        'read' => TRUE,
-        'write' => FALSE,
       ),
     ));
-    $this->register('config.storage.dispatcher', 'Drupal\Core\Config\StorageDispatcher')
-      ->addArgument('%config.storage.info%');
+    // @todo Use this for exporting your current config in the DB... ;)
+    #$this->register('config.storage', 'Drupal\Core\Config\MultiStorage')
+    $this->register('config.storage', 'Drupal\Core\Config\CachedFileStorage')
+      ->addArgument('%config.storage.options%');
 
     // Register configuration object factory.
     $this->register('config.factory', 'Drupal\Core\Config\ConfigFactory')
-      ->addArgument(new Reference('config.storage.dispatcher'));
+      ->addArgument(new Reference('config.storage'));
 
     // Register the HTTP kernel services.
     $this->register('dispatcher', 'Symfony\Component\EventDispatcher\EventDispatcher')
diff --git a/core/modules/config/config.admin.inc b/core/modules/config/config.admin.inc
new file mode 100644
index 0000000..30cc090
--- /dev/null
+++ b/core/modules/config/config.admin.inc
@@ -0,0 +1,129 @@
+<?php
+
+/**
+ * @file
+ * Admin page callbacks for the config module.
+ */
+
+use Drupal\Core\Config\FileStorage;
+use Drupal\Core\Config\StorageInterface;
+
+/**
+ * Helper function to construct the storage changes in a configuration synchronization form.
+ *
+ * @param array $form
+ *   The form structure to add to. Passed by reference.
+ * @param array $form_state
+ *   The current state of the form. Passed by reference.
+ * @param Drupal\Core\Config\StorageInterface $source_storage
+ *   The source storage to retrieve differences from.
+ * @param Drupal\Core\Config\StorageInterface $target_storage
+ *   The target storage to compare differences to.
+ */
+function config_admin_sync_form(array &$form, array &$form_state, StorageInterface $source_storage, StorageInterface $target_storage) {
+  $config_changes = config_sync_get_changes($source_storage, $target_storage);
+  if (empty($config_changes)) {
+    $form['no_changes'] = array(
+      '#markup' => t('There are no configuration changes.'),
+    );
+    return $form;
+  }
+
+  foreach ($config_changes as $config_change_type => $config_files) {
+    if (empty($config_files)) {
+      continue;
+    }
+    $form[$config_change_type] = array(
+      '#type' => 'fieldset',
+      '#title' => $config_change_type . ' (' . count($config_files) . ')',
+      '#collapsible' => TRUE,
+    );
+    $form[$config_change_type]['config_files'] = array(
+      '#theme' => 'table',
+      '#header' => array('Name'),
+    );
+    foreach ($config_files as $config_file) {
+      $form[$config_change_type]['config_files']['#rows'][] = array($config_file);
+    }
+  }
+}
+
+/**
+ * Form constructor for configuration import form.
+ *
+ * @see config_admin_import_form_submit()
+ */
+function config_admin_import_form($form, &$form_state) {
+  // Retrieve a list of differences between FileStorage and the active store.
+  $source_storage = new FileStorage();
+  $target_storage = drupal_container()->get('config.storage');
+
+  // Prevent users from deleting all configuration.
+  // If the source storage is empty, that signals the unique condition of not
+  // having exported anything at all, and thus no valid storage to compare the
+  // active storage against.
+  // @todo StorageInterface::listAll() can easily yield hundreds or even
+  //   thousands of entries; consider to add a dedicated isEmpty() method for
+  //   storage controllers.
+  $all = $source_storage->listAll();
+  if (empty($all)) {
+    form_set_error('', t('There is no base configuration. <a href="@export-url">Export</a> it first.', array(
+      '@export-url' => url('admin/config/development/sync/export'),
+    )));
+    return $form;
+  }
+
+  config_admin_sync_form($form, $form_state, $source_storage, $target_storage);
+
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Import'),
+  );
+  return $form;
+}
+
+/**
+ * Form submission handler for config_admin_import_form().
+ */
+function config_admin_import_form_submit($form, &$form_state) {
+  if (config_import()) {
+    drupal_set_message(t('The configuration was imported successfully.'));
+  }
+  else {
+    // Another request may be synchronizing configuration already. Wait for it
+    // to complete before returning the error, so already synchronized changes
+    // do not appear again.
+    lock_wait(__FUNCTION__);
+    drupal_set_message(t('The import failed due to an error. Any errors have been logged.'), 'error');
+  }
+}
+
+/**
+ * Form constructor for configuration export form.
+ *
+ * @see config_admin_export_form_submit()
+ */
+function config_admin_export_form($form, &$form_state) {
+  // Retrieve a list of differences between the active store and FileStorage.
+  $source_storage = drupal_container()->get('config.storage');
+  $target_storage = new FileStorage();
+
+  config_admin_sync_form($form, $form_state, $source_storage, $target_storage);
+
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['submit'] = array(
+    '#type' => 'submit',
+    '#value' => t('Export'),
+  );
+  return $form;
+}
+
+/**
+ * Form submission handler for config_admin_export_form().
+ */
+function config_admin_export_form_submit($form, &$form_state) {
+  config_export();
+  drupal_set_message(t('The configuration was exported successfully.'));
+}
+
diff --git a/core/modules/config/config.info b/core/modules/config/config.info
index 380f17e..efab7a1 100644
--- a/core/modules/config/config.info
+++ b/core/modules/config/config.info
@@ -3,3 +3,4 @@ description = Allows administrators to manage configuration changes.
 package = Core
 version = VERSION
 core = 8.x
+configure = admin/config/development/sync
diff --git a/core/modules/config/config.module b/core/modules/config/config.module
index b3d9bbc..fd38fd2 100644
--- a/core/modules/config/config.module
+++ b/core/modules/config/config.module
@@ -1 +1,46 @@
 <?php
+
+/**
+ * @file
+ * Allows site administrators to modify configuration.
+ */
+
+/**
+ * Implements hook_permission().
+ */
+function config_permission() {
+  $permissions['synchronize configuration'] = array(
+    'title' => t('Synchronize configuration'),
+    'restrict access' => TRUE,
+  );
+  return $permissions;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function config_menu() {
+  $items['admin/config/development/sync'] = array(
+    'title' => 'Synchronize configuration',
+    'description' => 'Synchronize configuration changes.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('config_admin_import_form'),
+    'access arguments' => array('synchronize configuration'),
+    'file' => 'config.admin.inc',
+  );
+  $items['admin/config/development/sync/import'] = array(
+    'title' => 'Import',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/config/development/sync/export'] = array(
+    'title' => 'Export',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('config_admin_export_form'),
+    'access arguments' => array('synchronize configuration'),
+    'file' => 'config.admin.inc',
+    'type' => MENU_LOCAL_TASK,
+  );
+  return $items;
+}
+
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigConfigurableTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigConfigurableTest.php
new file mode 100644
index 0000000..cfb4bcc
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigConfigurableTest.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\config\Tests\ConfigConfigurableTest.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests configurable configuration.
+ */
+class ConfigConfigurableTest extends WebTestBase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Configurable configuration',
+      'description' => 'Tests configurable configuration.',
+      'group' => 'Configuration',
+    );
+  }
+
+  function setUp() {
+    parent::setUp(array('config_test'));
+  }
+
+  /**
+   * Tests basic CRUD operations through the UI.
+   */
+  function testCRUD() {
+    // Create a thingie.
+    $id = 'thingie';
+    $edit = array(
+      'id' => $id,
+      'label' => 'Thingie',
+    );
+    $this->drupalPost('admin/structure/config_test/add', $edit, 'Save');
+    $this->assertResponse(200);
+    $this->assertText('Thingie');
+
+    // Update the thingie.
+    $this->assertLinkByHref('admin/structure/config_test/manage/' . $id);
+    $edit = array(
+      'label' => 'Thongie',
+    );
+    $this->drupalPost('admin/structure/config_test/manage/' . $id, $edit, 'Save');
+    $this->assertResponse(200);
+    $this->assertNoText('Thingie');
+    $this->assertText('Thongie');
+
+    // Delete the thingie.
+    $this->assertLinkByHref('admin/structure/config_test/manage/' . $id . '/delete');
+    $this->drupalPost('admin/structure/config_test/manage/' . $id . '/delete', array(), 'Delete');
+    $this->assertResponse(200);
+    $this->assertNoText('Thingie');
+    $this->assertNoText('Thongie');
+
+    // Re-create a thingie.
+    $edit = array(
+      'id' => $id,
+      'label' => 'Thingie',
+    );
+    $this->drupalPost('admin/structure/config_test/add', $edit, 'Save');
+    $this->assertResponse(200);
+    $this->assertText('Thingie');
+
+    // Rename the thingie's ID/machine name.
+    $this->assertLinkByHref('admin/structure/config_test/manage/' . $id);
+    $new_id = 'zingie';
+    $edit = array(
+      'id' => $new_id,
+      'label' => 'Zingie',
+    );
+    $this->drupalPost('admin/structure/config_test/manage/' . $id, $edit, 'Save');
+    $this->assertResponse(200);
+    $this->assertNoText('Thingie');
+    $this->assertText('Zingie');
+  }
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php
index a6e72b4..88857cd 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php
@@ -25,6 +25,29 @@ class ConfigImportTest extends WebTestBase {
 
   function setUp() {
     parent::setUp(array('config_test'));
+
+    // Clear out any possibly existing hook invocation records.
+    unset($GLOBALS['hook_config_test_dynamic']);
+  }
+
+  /**
+   * Tests omission of module APIs for bare configuration operations.
+   */
+  function testNoImport() {
+    $dynamic_name = 'config_test.dynamic.default';
+
+    // Verify the default configuration values exist.
+    $config = config($dynamic_name);
+    $this->assertIdentical($config->get('id'), 'default');
+
+    // Verify that a bare config() does not involve module APIs.
+    $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']));
+
+    // Export.
+    config_export();
+
+    // Verify that config_export() does not involve module APIs.
+    $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']));
   }
 
   /**
@@ -60,6 +83,14 @@ class ConfigImportTest extends WebTestBase {
     $this->assertIdentical($config->get('foo'), NULL);
     $config = config($dynamic_name);
     $this->assertIdentical($config->get('id'), NULL);
+
+    // Verify that appropriate module API hooks have been invoked.
+    $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['load']));
+    $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['presave']));
+    $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['insert']));
+    $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['update']));
+    $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['predelete']));
+    $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['delete']));
   }
 
   /**
@@ -69,22 +100,28 @@ class ConfigImportTest extends WebTestBase {
     $name = 'config_test.new';
     $dynamic_name = 'config_test.dynamic.new';
 
+    // Export.
+    config_export();
+
     // Verify the configuration to create does not exist yet.
+    $database_storage = new DatabaseStorage();
+    $this->assertIdentical($database_storage->exists($name), FALSE, $name . ' not found.');
+    $this->assertIdentical($database_storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.');
+
     $file_storage = new FileStorage();
     $this->assertIdentical($file_storage->exists($name), FALSE, $name . ' not found.');
     $this->assertIdentical($file_storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.');
 
-    // Export.
-    config_export();
-
     // Create new configuration objects.
-    $file_storage->write($name, array(
+    $original_name_data = array(
       'add_me' => 'new value',
-    ));
-    $file_storage->write($dynamic_name, array(
+    );
+    $file_storage->write($name, $original_name_data);
+    $original_dynamic_data = array(
       'id' => 'new',
       'label' => 'New',
-    ));
+    );
+    $file_storage->write($dynamic_name, $original_dynamic_data);
     $this->assertIdentical($file_storage->exists($name), TRUE, $name . ' found.');
     $this->assertIdentical($file_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
 
@@ -93,9 +130,17 @@ class ConfigImportTest extends WebTestBase {
 
     // Verify the values appeared.
     $config = config($name);
-    $this->assertIdentical($config->get('add_me'), 'new value');
+    $this->assertIdentical($config->get('add_me'), $original_name_data['add_me']);
     $config = config($dynamic_name);
-    $this->assertIdentical($config->get('label'), 'New');
+    $this->assertIdentical($config->get('label'), $original_dynamic_data['label']);
+
+    // Verify that appropriate module API hooks have been invoked.
+    $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['load']));
+    $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['presave']));
+    $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['insert']));
+    $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['update']));
+    $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['predelete']));
+    $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['delete']));
   }
 
   /**
@@ -108,17 +153,25 @@ class ConfigImportTest extends WebTestBase {
     // Export.
     config_export();
 
-    // Replace the file content of the existing configuration objects.
+    // Verify that the configuration objects to import exist.
+    $database_storage = new DatabaseStorage();
+    $this->assertIdentical($database_storage->exists($name), TRUE, $name . ' found.');
+    $this->assertIdentical($database_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
+
     $file_storage = new FileStorage();
     $this->assertIdentical($file_storage->exists($name), TRUE, $name . ' found.');
     $this->assertIdentical($file_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
-    $file_storage->write($name, array(
+
+    // Replace the file content of the existing configuration objects.
+    $original_name_data = array(
       'foo' => 'beer',
-    ));
-    $file_storage->write($dynamic_name, array(
+    );
+    $file_storage->write($name, $original_name_data);
+    $original_dynamic_data = array(
       'id' => 'default',
       'label' => 'Updated',
-    ));
+    );
+    $file_storage->write($dynamic_name, $original_dynamic_data);
 
     // Verify the active store still returns the default values.
     $config = config($name);
@@ -134,5 +187,17 @@ class ConfigImportTest extends WebTestBase {
     $this->assertIdentical($config->get('foo'), 'beer');
     $config = config($dynamic_name);
     $this->assertIdentical($config->get('label'), 'Updated');
+
+    // Verify that the original file content is still the same.
+    $this->assertIdentical($file_storage->read($name), $original_name_data);
+    $this->assertIdentical($file_storage->read($dynamic_name), $original_dynamic_data);
+
+    // Verify that appropriate module API hooks have been invoked.
+    $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['load']));
+    $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['presave']));
+    $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['insert']));
+    $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['update']));
+    $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['predelete']));
+    $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['delete']));
   }
 }
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
new file mode 100644
index 0000000..3a9acdd
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
@@ -0,0 +1,143 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\config\Tests\ConfigImportUITest.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\Core\Config\FileStorage;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests importing configuration from files into active store.
+ */
+class ConfigImportUITest extends WebTestBase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Import/Export UI',
+      'description' => 'Tests the user interface for importing/exporting configuration.',
+      'group' => 'Configuration',
+    );
+  }
+
+  function setUp() {
+    parent::setUp(array('config', 'config_test'));
+
+    $this->web_user = $this->drupalCreateUser(array('synchronize configuration'));
+    $this->drupalLogin($this->web_user);
+  }
+
+  /**
+   * Tests exporting configuration.
+   */
+  function testExport() {
+    $name = 'config_test.system';
+    $dynamic_name = 'config_test.dynamic.default';
+
+    // Verify the default configuration values exist.
+    $config = config($name);
+    $this->assertIdentical($config->get('foo'), 'bar');
+    $config = config($dynamic_name);
+    $this->assertIdentical($config->get('id'), 'default');
+
+    // Verify that both appear as deleted by default.
+    $this->drupalGet('admin/config/development/sync/export');
+    $this->assertText($name);
+    $this->assertText($dynamic_name);
+
+    // Export and verify that both do not appear anymore.
+    $this->drupalPost(NULL, array(), t('Export'));
+    $this->assertUrl('admin/config/development/sync/export');
+    $this->assertNoText($name);
+    $this->assertNoText($dynamic_name);
+
+    // Verify that there are no further changes to export.
+    $this->assertText(t('There are no configuration changes.'));
+
+    // Verify that the import screen shows no changes either.
+    $this->drupalGet('admin/config/development/sync');
+    $this->assertText(t('There are no configuration changes.'));
+  }
+
+  /**
+   * Tests importing configuration.
+   */
+  function testImport() {
+    $name = 'config_test.new';
+    $dynamic_name = 'config_test.dynamic.new';
+
+    // Verify the configuration to create does not exist yet.
+    $file_storage = new FileStorage();
+    $this->assertIdentical($file_storage->exists($name), FALSE, $name . ' not found.');
+    $this->assertIdentical($file_storage->exists($dynamic_name), FALSE, $dynamic_name . ' not found.');
+
+    // Verify that the import UI does not allow to import without exported
+    // configuration.
+    $this->drupalGet('admin/config/development/sync');
+    $this->assertText('There is no base configuration.');
+
+    // Verify that the Export link yields to the export UI page, and export.
+    $this->clickLink('Export');
+    $this->drupalPost(NULL, array(), t('Export'));
+
+    // Create new configuration objects.
+    $file_storage->write($name, array(
+      'add_me' => 'new value',
+    ));
+    $file_storage->write($dynamic_name, array(
+      'id' => 'new',
+      'label' => 'New',
+    ));
+    $this->assertIdentical($file_storage->exists($name), TRUE, $name . ' found.');
+    $this->assertIdentical($file_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
+
+    // Verify that both appear as new.
+    $this->drupalGet('admin/config/development/sync');
+    $this->assertText($name);
+    $this->assertText($dynamic_name);
+
+    // Import and verify that both do not appear anymore.
+    $this->drupalPost(NULL, array(), t('Import'));
+    $this->assertUrl('admin/config/development/sync');
+    $this->assertNoText($name);
+    $this->assertNoText($dynamic_name);
+
+    // Verify that there are no further changes to import.
+    $this->assertText(t('There are no configuration changes.'));
+
+    // Verify that the export screen shows no changes either.
+    $this->drupalGet('admin/config/development/sync/export');
+    $this->assertText(t('There are no configuration changes.'));
+  }
+
+  /**
+   * Tests concurrent importing of configuration.
+   */
+  function testImportLock() {
+    $name = 'config_test.new';
+
+    // Write a configuration object to import.
+    $file_storage = new FileStorage();
+    $file_storage->write($name, array(
+      'add_me' => 'new value',
+    ));
+
+    // Verify that there are configuration differences to import.
+    $this->drupalGet('admin/config/development/sync');
+    $this->assertNoText(t('There are no configuration changes.'));
+
+    // Acquire a fake-lock on the import mechanism.
+    $lock_name = 'config_import';
+    lock_acquire($lock_name);
+
+    // Attempt to import configuration and verify that an error message appears.
+    $this->drupalPost(NULL, array(), t('Import'));
+    $this->assertUrl('admin/config/development/sync');
+    $this->assertText(t('The import failed due to an error. Any errors have been logged.'));
+
+    // Release the lock, just to keep testing sane.
+    lock_release($lock_name);
+  }
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php
index 7ec6d8e..a9676ef 100644
--- a/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php
@@ -46,5 +46,14 @@ class ConfigInstallTest extends WebTestBase {
     // Verify that configuration import callback was invoked for the dynamic
     // thingie.
     $this->assertTrue($GLOBALS['hook_config_import']);
+
+    // Verify that config_test API hooks were invoked for the dynamic default
+    // thingie.
+    $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['load']));
+    $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['presave']));
+    $this->assertTrue(isset($GLOBALS['hook_config_test_dynamic']['insert']));
+    $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['update']));
+    $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['predelete']));
+    $this->assertFalse(isset($GLOBALS['hook_config_test_dynamic']['delete']));
   }
 }
diff --git a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
index 2dbc627..e1d77be 100644
--- a/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
+++ b/core/modules/config/lib/Drupal/config/Tests/Storage/ConfigStorageTestBase.php
@@ -32,6 +32,9 @@ abstract class ConfigStorageTestBase extends WebTestBase {
   function testCRUD() {
     $name = 'config_test.storage';
 
+    // Checking whether a non-existing name exists returns FALSE.
+    $this->assertIdentical($this->storage->exists($name), FALSE);
+
     // Reading a non-existing name returns FALSE.
     $data = $this->storage->read($name);
     $this->assertIdentical($data, FALSE);
@@ -51,9 +54,13 @@ abstract class ConfigStorageTestBase extends WebTestBase {
     $data = array('foo' => 'bar');
     $result = $this->storage->write($name, $data);
     $this->assertIdentical($result, TRUE);
+
     $raw_data = $this->read($name);
     $this->assertIdentical($raw_data, $data);
 
+    // Checking whether an existing name exists returns TRUE.
+    $this->assertIdentical($this->storage->exists($name), TRUE);
+
     // Writing the identical data again still returns TRUE.
     $result = $this->storage->write($name, $data);
     $this->assertIdentical($result, TRUE);
diff --git a/core/modules/config/tests/config_test/config/config_test.delete.yml b/core/modules/config/tests/config_test/config/config_test.delete.yml
new file mode 100644
index 0000000..b8ccb67
--- /dev/null
+++ b/core/modules/config/tests/config_test/config/config_test.delete.yml
@@ -0,0 +1 @@
+delete_me: bar
diff --git a/core/modules/config/tests/config_test/config_test.hooks.inc b/core/modules/config/tests/config_test/config_test.hooks.inc
new file mode 100644
index 0000000..2ec6831
--- /dev/null
+++ b/core/modules/config/tests/config_test/config_test.hooks.inc
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Fake third-party hook implementations for ConfigTest/config_test thingies.
+ *
+ * Testing the module/hook system is not the purpose of this test helper module.
+ * Therefore, this file implements hooks on behalf of config_test module for
+ * config_test thingie hooks themselves.
+ */
+
+/**
+ * Implements hook_config_test_dynamic_load().
+ */
+function config_test_config_test_dynamic_load() {
+  $GLOBALS['hook_config_test_dynamic']['load'] = __FUNCTION__;
+}
+
+/**
+ * Implements hook_config_test_dynamic_presave().
+ */
+function config_test_config_test_dynamic_presave() {
+  $GLOBALS['hook_config_test_dynamic']['presave'] = __FUNCTION__;
+}
+
+/**
+ * Implements hook_config_test_dynamic_insert().
+ */
+function config_test_config_test_dynamic_insert() {
+  $GLOBALS['hook_config_test_dynamic']['insert'] = __FUNCTION__;
+}
+
+/**
+ * Implements hook_config_test_dynamic_update().
+ */
+function config_test_config_test_dynamic_update() {
+  $GLOBALS['hook_config_test_dynamic']['update'] = __FUNCTION__;
+}
+
+/**
+ * Implements hook_config_test_dynamic_predelete().
+ */
+function config_test_config_test_dynamic_predelete() {
+  $GLOBALS['hook_config_test_dynamic']['predelete'] = __FUNCTION__;
+}
+
+/**
+ * Implements hook_config_test_dynamic_delete().
+ */
+function config_test_config_test_dynamic_delete() {
+  $GLOBALS['hook_config_test_dynamic']['delete'] = __FUNCTION__;
+}
diff --git a/core/modules/config/tests/config_test/config_test.module b/core/modules/config/tests/config_test/config_test.module
index 6fd84a2..fe18c4a 100644
--- a/core/modules/config/tests/config_test/config_test.module
+++ b/core/modules/config/tests/config_test/config_test.module
@@ -1,5 +1,9 @@
 <?php
 
+use Drupal\config_test\ConfigTest;
+
+require_once dirname(__FILE__) . '/config_test.hooks.inc';
+
 /**
  * Implements MODULE_config_import_create().
  */
@@ -12,7 +16,8 @@ function config_test_config_import_create($name, $new_config, $old_config) {
   // Set a global value we can check in test code.
   $GLOBALS['hook_config_import'] = __FUNCTION__;
 
-  $new_config->save();
+  $config_test = new ConfigTest($new_config);
+  $config_test->save();
   return TRUE;
 }
 
@@ -28,7 +33,9 @@ function config_test_config_import_change($name, $new_config, $old_config) {
   // Set a global value we can check in test code.
   $GLOBALS['hook_config_import'] = __FUNCTION__;
 
-  $new_config->save();
+  $config_test = new ConfigTest($new_config);
+  $config_test->setOriginal($old_config);
+  $config_test->save();
   return TRUE;
 }
 
@@ -44,7 +51,208 @@ function config_test_config_import_delete($name, $new_config, $old_config) {
   // Set a global value we can check in test code.
   $GLOBALS['hook_config_import'] = __FUNCTION__;
 
-  $old_config->delete();
+  $config_test = new ConfigTest($old_config);
+  $config_test->delete();
   return TRUE;
 }
 
+/**
+ * Implements hook_menu().
+ */
+function config_test_menu() {
+  $items['admin/structure/config_test'] = array(
+    'title' => 'Test configuration',
+    'page callback' => 'config_test_list_page',
+    'access callback' => TRUE,
+  );
+  $items['admin/structure/config_test/add'] = array(
+    'title' => 'Add test configuration',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('config_test_form'),
+    'access callback' => TRUE,
+    'type' => MENU_LOCAL_ACTION,
+  );
+  $items['admin/structure/config_test/manage/%config_test'] = array(
+    'title' => 'Edit test configuration',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('config_test_form', 4),
+    'access callback' => TRUE,
+  );
+  $items['admin/structure/config_test/manage/%config_test/edit'] = array(
+    'title' => 'Edit',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/structure/config_test/manage/%config_test/delete'] = array(
+    'title' => 'Delete',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('config_test_delete_form', 4),
+    'access callback' => TRUE,
+    'type' => MENU_LOCAL_TASK,
+  );
+  return $items;
+}
+
+/**
+ * Loads a ConfigTest object.
+ *
+ * @param string $id
+ *   The ID of the ConfigTest object to load.
+ */
+function config_test_load($id) {
+  $config = config('config_test.dynamic.' . $id);
+  if ($config->isNew()) {
+    return FALSE;
+  }
+  return new ConfigTest($config);
+}
+
+/**
+ * Saves a ConfigTest object.
+ *
+ * @param Drupal\config_test\ConfigTest $config_test
+ *   The ConfigTest object to save.
+ */
+function config_test_save(ConfigTest $config_test) {
+  return $config_test->save();
+}
+
+/**
+ * Deletes a ConfigTest object.
+ *
+ * @param string $id
+ *   The ID of the ConfigTest object to delete.
+ */
+function config_test_delete($id) {
+  $config = config('config_test.dynamic.' . $id);
+  $config_test = new ConfigTest($config);
+  return $config_test->delete();
+}
+
+/**
+ * Page callback; Lists available ConfigTest objects.
+ */
+function config_test_list_page() {
+  $config_names = config_get_storage_names_with_prefix('config_test.dynamic.');
+  $rows = array();
+  foreach ($config_names as $config_name) {
+    $config_test = new ConfigTest(config($config_name));
+    $row = array();
+    $row['name']['data'] = array(
+      '#type' => 'link',
+      '#title' => $config_test->getLabel(),
+      '#href' => $config_test->getUri(),
+    );
+    $row['delete']['data'] = array(
+      '#type' => 'link',
+      '#title' => t('Delete'),
+      '#href' => $config_test->getUri() . '/delete',
+    );
+    $rows[] = $row;
+  }
+  $build = array(
+    '#theme' => 'table',
+    '#header' => array('Name', 'Operations'),
+    '#rows' => $rows,
+    '#empty' => format_string('No test configuration defined. <a href="@add-url">Add some</a>', array(
+      '@add-url' => url('admin/structure/config_test/add'),
+    )),
+  );
+  return $build;
+}
+
+/**
+ * Form constructor to add or edit a ConfigTest object.
+ *
+ * @param Drupal\config_test\ConfigTest $config_test
+ *   (optional) An existing ConfigTest object to edit. If omitted, the form
+ *   creates a new ConfigTest.
+ */
+function config_test_form($form, &$form_state, ConfigTest $config_test = NULL) {
+  if (!isset($config_test)) {
+    $config_test = new ConfigTest(config(NULL));
+  }
+  $form_state['config_test'] = $config_test;
+
+  $form['label'] = array(
+    '#type' => 'textfield',
+    '#title' => 'Label',
+    '#default_value' => $config_test->getLabel(),
+    '#required' => TRUE,
+  );
+  $form['id'] = array(
+    '#type' => 'machine_name',
+    '#default_value' => $config_test->getID(),
+    '#required' => TRUE,
+    '#machine_name' => array(
+      'exists' => 'config_test_load',
+      // @todo Update form_process_machine_name() to use 'label' by default.
+      'source' => array('label'),
+    ),
+  );
+  $form['style'] = array(
+    '#type' => 'select',
+    '#title' => 'Image style',
+    '#options' => array(),
+    '#default_value' => $config_test->get('style'),
+    '#access' => FALSE,
+  );
+  if (module_exists('image')) {
+    $form['style']['#access'] = TRUE;
+    $form['style']['#options'] = image_style_options();
+  }
+
+  $form['actions'] = array('#type' => 'actions');
+  $form['actions']['submit'] = array('#type' => 'submit', '#value' => 'Save');
+
+  return $form;
+}
+
+/**
+ * Form submission handler for config_test_form().
+ */
+function config_test_form_submit($form, &$form_state) {
+  form_state_values_clean($form_state);
+
+  $config_test = $form_state['config_test'];
+
+  foreach ($form_state['values'] as $key => $value) {
+    $config_test->set($key, $value);
+  }
+  $config_test->save();
+
+  if (!empty($config_test->original)) {
+    drupal_set_message(format_string('%label configuration has been updated.', array('%label' => $config_test->getLabel())));
+  }
+  else {
+    drupal_set_message(format_string('%label configuration has been created.', array('%label' => $config_test->getLabel())));
+  }
+
+  $form_state['redirect'] = 'admin/structure/config_test';
+}
+
+/**
+ * Form constructor to delete a ConfigTest object.
+ *
+ * @param Drupal\config_test\ConfigTest $config_test
+ *   The ConfigTest object to delete.
+ */
+function config_test_delete_form($form, &$form_state, ConfigTest $config_test) {
+  $form_state['config_test'] = $config_test;
+
+  $form['id'] = array('#type' => 'value', '#value' => $config_test->getID());
+  return confirm_form($form,
+    format_string('Are you sure you want to delete %label', array('%label' => $config_test->getLabel())),
+    'admin/structure/config_test',
+    NULL,
+    'Delete'
+  );
+}
+
+/**
+ * Form submission handler for config_test_delete_form().
+ */
+function config_test_delete_form_submit($form, &$form_state) {
+  $form_state['config_test']->delete();
+  $form_state['redirect'] = 'admin/structure/config_test';
+}
diff --git a/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTest.php b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTest.php
new file mode 100644
index 0000000..4efaae5
--- /dev/null
+++ b/core/modules/config/tests/config_test/lib/Drupal/config_test/ConfigTest.php
@@ -0,0 +1,36 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\config_test\ConfigTest.
+ */
+
+namespace Drupal\config_test;
+
+use Drupal\Core\ConfigThingie\ConfigThingieBase;
+
+/**
+ * Defines the ConfigTest configuration thingie.
+ */
+class ConfigTest extends ConfigThingieBase {
+  /**
+   * The image style to use.
+   *
+   * @var string
+   */
+  public $style;
+
+  /**
+   * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::getConfigPrefix().
+   */
+  public function getConfigPrefix() {
+    return 'config_test.dynamic';
+  }
+
+  /**
+   * Implements Drupal\Core\ConfigThingie\ConfigThingieInterface::getURI().
+   */
+  public function getURI() {
+    return 'admin/structure/config_test/manage/' . $this->getID();
+  }
+}
diff --git a/profiles/standard/standard.info b/profiles/standard/standard.info
index 8b8a33b..c5240dd 100644
--- a/profiles/standard/standard.info
+++ b/profiles/standard/standard.info
@@ -6,6 +6,7 @@ dependencies[] = node
 dependencies[] = block
 dependencies[] = color
 dependencies[] = comment
+dependencies[] = config
 dependencies[] = contextual
 dependencies[] = help
 dependencies[] = image
