diff --git a/core/includes/config.inc b/core/includes/config.inc
index c6435b3..e17c788 100644
--- a/core/includes/config.inc
+++ b/core/includes/config.inc
@@ -1,7 +1,11 @@
 <?php
 
+use Drupal\Core\Config\Config;
+use Drupal\Core\Config\ConfigException;
 use Drupal\Core\Config\DatabaseStorage;
 use Drupal\Core\Config\FileStorage;
+use Drupal\Core\Config\NullStorage;
+use Drupal\Core\Config\StorageInterface;
 
 /**
  * @file
@@ -20,13 +24,25 @@ use Drupal\Core\Config\FileStorage;
 function config_install_default_config($module) {
   $module_config_dir = drupal_get_path('module', $module) . '/config';
   if (is_dir($module_config_dir)) {
-    $database_storage = new DatabaseStorage();
-    $module_file_storage = new FileStorage(array('directory' => $module_config_dir));
+    $source_storage = new FileStorage(array('directory' => $module_config_dir));
+    $target_storage = new DatabaseStorage();
+    $null_storage = new NullStorage();
 
-    foreach ($module_file_storage->listAll() as $config_name) {
-      $data = $module_file_storage->read($config_name);
-      $database_storage->write($config_name, $data);
+    // Upon module installation, only new config objects need to be created.
+    // config_sync_get_changes() would potentially perform a diff of hundreds or
+    // even thousands of config objects that happen to be contained in the
+    // active store. We leverage the NullStorage to avoid that needless
+    // computation of differences.
+    $config_changes = config_sync_get_changes($source_storage, $null_storage);
+    if (empty($config_changes)) {
+      return;
     }
+    // Upon re-installation, potentially stale and pre-existing config objects,
+    // having the same names as default configuration objects, need to be
+    // deleted before creating the new default config.
+    $config_changes['delete'] = $config_changes['create'];
+    $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
+    config_sync_changes($remaining_changes, $source_storage, $target_storage);
   }
 }
 
@@ -57,3 +73,172 @@ function config($name) {
   return drupal_container()->get('config.factory')->get($name)->load();
 }
 
+/**
+ * Returns a list of differences between configuration storages.
+ *
+ * @param Drupal\Core\Config\StorageInterface $source_storage
+ *   The storage to synchronize configuration from.
+ * @param Drupal\Core\Config\StorageInterface $target_storage
+ *   The storage to synchronize configuration to.
+ *
+ * @return array|bool
+ *   An assocative array containing the differences between source and target
+ *   storage, or FALSE if there are no differences.
+ */
+function config_sync_get_changes(StorageInterface $source_storage, StorageInterface $target_storage) {
+  $source_names = $source_storage->listAll();
+  $target_names = $target_storage->listAll();
+  $config_changes = array(
+    'create' => array_diff($source_names, $target_names),
+    'change' => array(),
+    'delete' => array_diff($target_names, $source_names),
+  );
+  foreach (array_intersect($source_names, $target_names) as $name) {
+    $source_config_data = $source_storage->read($name);
+    $target_config_data = $target_storage->read($name);
+    if ($source_config_data != $target_config_data) {
+      $config_changes['change'][] = $name;
+    }
+  }
+
+  // Do not trigger subsequent synchronization operations if there are no
+  // changes in either category.
+  if (empty($config_changes['create']) && empty($config_changes['change']) && empty($config_changes['delete'])) {
+    return FALSE;
+  }
+  return $config_changes;
+}
+
+/**
+ * Writes an array of config file changes from a source storage to a target storage.
+ *
+ * @param array $config_changes
+ *   An array of changes to be written.
+ * @param Drupal\Core\Config\StorageInterface $source_storage
+ *   The storage to synchronize configuration from.
+ * @param Drupal\Core\Config\StorageInterface $target_storage
+ *   The storage to synchronize configuration to.
+ */
+function config_sync_changes(array $config_changes, StorageInterface $source_storage, StorageInterface $target_storage) {
+  foreach (array('delete', 'create', 'change') as $op) {
+    foreach ($config_changes[$op] as $name) {
+      if ($op == 'delete') {
+        $target_storage->delete($name);
+      }
+      else {
+        $data = $source_storage->read($name);
+        $target_storage->write($name, $data);
+      }
+    }
+  }
+}
+
+/**
+ * Imports configuration from FileStorage to DatabaseStorage.
+ */
+function config_import() {
+  // Retrieve a list of differences between FileStorage and DatabaseStorage.
+  // @todo Leverage DI + config.storage.info.
+  $source_storage = new FileStorage();
+  $target_storage = new DatabaseStorage();
+
+  $config_changes = config_sync_get_changes($source_storage, $target_storage);
+  if (empty($config_changes)) {
+    return;
+  }
+
+  if (!lock_acquire(__FUNCTION__)) {
+    // Another request is synchronizing configuration.
+    // Return a negative result for UI purposes. We do not make a difference
+    // between an actual synchronization error and a failed lock, because a
+    // concurrent request synchronizing configuration is an edge-case in the
+    // first place and would mean that more than one developer or site builder
+    // attempts to do it without coordinating with others.
+    return FALSE;
+  }
+
+  try {
+    $remaining_changes = config_import_invoke_owner($config_changes, $source_storage, $target_storage);
+    config_sync_changes($remaining_changes, $source_storage, $target_storage);
+    // Flush all caches and reset static variables after a successful import.
+    drupal_flush_all_caches();
+  }
+  catch (ConfigException $e) {
+    watchdog_exception('config_import', $e);
+    lock_release(__FUNCTION__);
+    return FALSE;
+  }
+  lock_release(__FUNCTION__);
+  return TRUE;
+}
+
+/**
+ * Invokes MODULE_config_import() callbacks for configuration changes.
+ *
+ * @param array $config_changes
+ *   An array of changes to be loaded.
+ * @param Drupal\Core\Config\StorageInterface $source_storage
+ *   The storage to synchronize configuration from.
+ * @param Drupal\Core\Config\StorageInterface $target_storage
+ *   The storage to synchronize configuration to.
+ *
+ * @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
+  // handle dependencies correctly.
+  foreach (array('delete', 'create', 'change') as $op) {
+    foreach ($config_changes[$op] as $key => $name) {
+      // Extract owner from configuration object name.
+      $module = strtok($name, '.');
+      // Check whether the module implements hook_config_import() and ask it to
+      // handle the configuration change.
+      $handled_by_module = FALSE;
+      if (module_hook($module, 'config_import')) {
+        $old_config = new Config($storage_dispatcher);
+        $old_config
+          ->setName($name)
+          ->load();
+        if ($op == 'delete' && $old_config->isNew()) {
+          continue;
+        }
+
+        $data = $source_storage->read($name);
+        if ($op == 'create' && $data === FALSE) {
+          continue;
+        }
+        $new_config = new Config($storage_dispatcher);
+        $new_config
+          ->setName($name)
+          ->setData($data);
+
+        $handled_by_module = module_invoke($module, 'config_import', $op, $name, $new_config, $old_config);
+      }
+      if (!empty($handled_by_module)) {
+        unset($config_changes[$op][$key]);
+      }
+    }
+  }
+  return $config_changes;
+}
+
+/**
+ * Exports configuration from DatabaseStorage to FileStorage.
+ */
+function config_export() {
+  // Retrieve a list of differences between DatabaseStorage and FileStorage.
+  // @todo Leverage DI + config.storage.info.
+  $source_storage = new DatabaseStorage();
+  $target_storage = new FileStorage();
+
+  $config_changes = config_sync_get_changes($source_storage, $target_storage);
+  if (empty($config_changes)) {
+    return;
+  }
+  config_sync_changes($config_changes, $source_storage, $target_storage);
+  return TRUE;
+}
diff --git a/core/lib/Drupal/Core/Config/Config.php b/core/lib/Drupal/Core/Config/Config.php
index 0748b76..457df57 100644
--- a/core/lib/Drupal/Core/Config/Config.php
+++ b/core/lib/Drupal/Core/Config/Config.php
@@ -20,6 +20,13 @@ class Config {
   protected $name;
 
   /**
+   * Whether the configuration object is new or has been saved to the storage.
+   *
+   * @var bool
+   */
+  protected $isNew = TRUE;
+
+  /**
    * The data of the configuration object.
    *
    * @var array
@@ -60,6 +67,13 @@ class Config {
   }
 
   /**
+   * Returns whether this configuration object is new.
+   */
+  public function isNew() {
+    return $this->isNew;
+  }
+
+  /**
    * Gets data from this config object.
    *
    * @param $key
@@ -210,6 +224,7 @@ class Config {
   public function load() {
     $this->setData(array());
     $data = $this->storageDispatcher->selectStorage('read', $this->name)->read($this->name);
+    $this->isNew = ($data === FALSE);
     if ($data !== FALSE) {
       $this->setData($data);
     }
@@ -222,6 +237,7 @@ class Config {
   public function save() {
     $this->sortByKey($this->data);
     $this->storageDispatcher->selectStorage('write', $this->name)->write($this->name, $this->data);
+    $this->isNew = FALSE;
     return $this;
   }
 
@@ -251,6 +267,7 @@ class Config {
   public function delete() {
     $this->data = array();
     $this->storageDispatcher->selectStorage('write', $this->name)->delete($this->name);
+    $this->isNew = TRUE;
     return $this;
   }
 }
diff --git a/core/lib/Drupal/Core/Config/DatabaseStorage.php b/core/lib/Drupal/Core/Config/DatabaseStorage.php
index 1dd8507..f30011c 100644
--- a/core/lib/Drupal/Core/Config/DatabaseStorage.php
+++ b/core/lib/Drupal/Core/Config/DatabaseStorage.php
@@ -51,7 +51,7 @@ class DatabaseStorage implements StorageInterface {
    *   Only thrown in case $this->options['throw_exception'] is TRUE.
    */
   public function read($name) {
-    $data = array();
+    $data = FALSE;
     // There are situations, like in the installer, where we may attempt a
     // read without actually having the database available. In this case,
     // catch the exception and just return an empty array so the caller can
@@ -114,7 +114,7 @@ class DatabaseStorage implements StorageInterface {
    */
   public static function decode($raw) {
     $data = @unserialize($raw);
-    return $data !== FALSE ? $data : array();
+    return is_array($data) ? $data : FALSE;
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php
index 7827dc0..d9e7a5a 100644
--- a/core/lib/Drupal/Core/Config/FileStorage.php
+++ b/core/lib/Drupal/Core/Config/FileStorage.php
@@ -70,18 +70,15 @@ class FileStorage implements StorageInterface {
    */
   public function read($name) {
     if (!$this->exists($name)) {
-      return array();
+      return FALSE;
     }
     $data = file_get_contents($this->getFilePath($name));
     // @todo Yaml throws a ParseException on invalid data. Is it expected to be
     //   caught or not?
     $data = $this->decode($data);
-    if ($data === FALSE) {
-      return array();
-    }
     // A simple string is valid YAML for any reason.
     if (!is_array($data)) {
-      return array();
+      return FALSE;
     }
     return $data;
   }
@@ -131,6 +128,7 @@ class FileStorage implements StorageInterface {
    * @throws Symfony\Component\Yaml\Exception\ParseException
    */
   public static function decode($raw) {
+    // @todo An empty file means bogus config data; return FALSE?
     if (empty($raw)) {
       return array();
     }
diff --git a/core/lib/Drupal/Core/Config/NullStorage.php b/core/lib/Drupal/Core/Config/NullStorage.php
new file mode 100644
index 0000000..54554c9
--- /dev/null
+++ b/core/lib/Drupal/Core/Config/NullStorage.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\Core\Config\NullStorage.
+ */
+
+namespace Drupal\Core\Config;
+
+/**
+ * Defines the Null storage controller.
+ */
+class NullStorage implements StorageInterface {
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::__construct().
+   */
+  public function __construct(array $options = array()) {
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::read().
+   */
+  public function read($name) {
+    return array();
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::write().
+   */
+  public function write($name, array $data) {
+    return FALSE;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::delete().
+   */
+  public function delete($name) {
+    return FALSE;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::encode().
+   */
+  public static function encode($data) {
+    return $data;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::decode().
+   */
+  public static function decode($raw) {
+    return $raw;
+  }
+
+  /**
+   * Implements Drupal\Core\Config\StorageInterface::listAll().
+   */
+  public function listAll($prefix = '') {
+    return array();
+  }
+}
diff --git a/core/lib/Drupal/Core/Config/StorageInterface.php b/core/lib/Drupal/Core/Config/StorageInterface.php
index 907e884..806ee87 100644
--- a/core/lib/Drupal/Core/Config/StorageInterface.php
+++ b/core/lib/Drupal/Core/Config/StorageInterface.php
@@ -30,9 +30,9 @@ interface StorageInterface {
    * @param string $name
    *   The name of a configuration object to load.
    *
-   * @return array
+   * @return array|bool
    *   The configuration data stored for the configuration object name. If no
-   *   configuration data exists for the given name, an empty array is returned.
+   *   configuration data exists for the given name, FALSE is returned.
    */
   public function read($name);
 
diff --git a/core/modules/config/config.admin.inc b/core/modules/config/config.admin.inc
new file mode 100644
index 0000000..6f77bf2
--- /dev/null
+++ b/core/modules/config/config.admin.inc
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Admin page callbacks for the config module.
+ */
+
+/**
+ * Form constructor for configuration import form.
+ *
+ * @see config_admin_import_form_submit()
+ */
+function config_admin_import_form($form, &$form_state) {
+  $config_changes = config_import_get_changes();
+
+  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['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');
+  }
+}
+
diff --git a/core/modules/config/config.api.php b/core/modules/config/config.api.php
new file mode 100644
index 0000000..c50c2f9
--- /dev/null
+++ b/core/modules/config/config.api.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the Configuration module.
+ */
+
+/**
+ * @defgroup config_hooks Configuration system hooks
+ * @{
+ * @todo Overall description of the configuration system.
+ * @}
+ */
+
+/**
+ * Synchronize configuration changes.
+ *
+ * This callback is invoked when configuration is synchronized between storages
+ * and allows a module to take over the synchronization of configuration data.
+ *
+ * Modules should implement this callback if they manage configuration data
+ * (such as image styles, node types, or fields), which needs to be
+ * prepared and passed through module API functions to properly handle a
+ * configuration change.
+ *
+ * @param string $op
+ *   The operation to perform for the configuration data; one of 'create',
+ *   'delete', or 'change'.
+ * @param string $name
+ *   The name of the configuration object.
+ * @param Drupal\Core\Config\DrupalConfig $new_config
+ *   A configuration object containing the new configuration data.
+ * @param Drupal\Core\Config\DrupalConfig $old_config
+ *   A configuration object containing the old configuration data.
+ */
+function MODULE_config_import($op, $name, $new_config, $old_config) {
+  // Only configurable thingies require custom handling. Any other module
+  // settings can be synchronized directly.
+  if (strpos($name, 'config_test.dynamic.') !== 0) {
+    return FALSE;
+  }
+
+  if ($op == 'delete') {
+    // @todo image_style_delete() supports the notion of a "replacement style"
+    //   to be used by other modules instead of the deleted style. Essential!
+    //   But that is impossible currently, since the config system only knows
+    //   about deleted and added changes. Introduce an 'old_ID' key within
+    //   config objects as a standard?
+    $config_test = new ConfigTest($old_config);
+    $config_test->delete();
+  }
+  if ($op == 'create') {
+    $config_test = new ConfigTest($new_config);
+    $config_test->save();
+  }
+  if ($op == 'change') {
+    $config_test = new ConfigTest($new_config);
+    $config_test->setOriginal($old_config);
+    $config_test->save();
+  }
+  return TRUE;
+}
+
diff --git a/core/modules/config/config.module b/core/modules/config/config.module
index b3d9bbc..3d4fcfe 100644
--- a/core/modules/config/config.module
+++ b/core/modules/config/config.module
@@ -1 +1,33 @@
 <?php
+
+/**
+ * @file
+ * Allows site administrators to modify configuration.
+ */
+
+/**
+ * Implements hook_permission().
+ */
+function config_permission() {
+  $permissions['import configuration'] = array(
+    'title' => t('Import configuration'),
+    'restrict access' => TRUE,
+  );
+  return $permissions;
+}
+
+/**
+ * Implements hook_menu().
+ */
+function config_menu() {
+  $items['admin/config/development/import'] = array(
+    'title' => 'Import configuration',
+    'description' => 'Import and synchronize configuration changes.',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('config_admin_import_form'),
+    'access arguments' => array('import configuration'),
+    'file' => 'config.admin.inc',
+  );
+  return $items;
+}
+
diff --git a/core/modules/config/config_test/config/config_test.delete.yml b/core/modules/config/config_test/config/config_test.delete.yml
new file mode 100644
index 0000000..b8ccb67
--- /dev/null
+++ b/core/modules/config/config_test/config/config_test.delete.yml
@@ -0,0 +1 @@
+delete_me: bar
diff --git a/core/modules/config/config_test/config/config_test.dynamic.default.yml b/core/modules/config/config_test/config/config_test.dynamic.default.yml
new file mode 100644
index 0000000..d285439
--- /dev/null
+++ b/core/modules/config/config_test/config/config_test.dynamic.default.yml
@@ -0,0 +1,2 @@
+id: default
+name: Default
diff --git a/core/modules/config/config_test/config/config_test.system.yml b/core/modules/config/config_test/config/config_test.system.yml
new file mode 100644
index 0000000..20e9ff3
--- /dev/null
+++ b/core/modules/config/config_test/config/config_test.system.yml
@@ -0,0 +1 @@
+foo: bar
diff --git a/core/modules/config/config_test/config_test.hooks.inc b/core/modules/config/config_test/config_test.hooks.inc
new file mode 100644
index 0000000..56f690f
--- /dev/null
+++ b/core/modules/config/config_test/config_test.hooks.inc
@@ -0,0 +1,45 @@
+<?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_load().
+ */
+function config_test_config_test_load() {
+  $GLOBALS['hook_config_test_load'] = __FUNCTION__;
+}
+
+/**
+ * Implements hook_config_test_presave().
+ */
+function config_test_config_test_presave() {
+  $GLOBALS['hook_config_test_presave'] = __FUNCTION__;
+}
+
+/**
+ * Implements hook_config_test_insert().
+ */
+function config_test_config_test_insert() {
+  $GLOBALS['hook_config_test_insert'] = __FUNCTION__;
+}
+
+/**
+ * Implements hook_config_test_update().
+ */
+function config_test_config_test_update() {
+  $GLOBALS['hook_config_test_update'] = __FUNCTION__;
+}
+
+/**
+ * Implements hook_config_test_delete().
+ */
+function config_test_config_test_delete() {
+  $GLOBALS['hook_config_test_delete'] = __FUNCTION__;
+}
diff --git a/core/modules/config/config_test/config_test.info b/core/modules/config/config_test/config_test.info
new file mode 100644
index 0000000..8735450
--- /dev/null
+++ b/core/modules/config/config_test/config_test.info
@@ -0,0 +1,6 @@
+name = Configuration test module
+package = Core
+version = VERSION
+core = 8.x
+dependencies[] = config
+hidden = TRUE
diff --git a/core/modules/config/config_test/config_test.module b/core/modules/config/config_test/config_test.module
new file mode 100644
index 0000000..8d4e8ec
--- /dev/null
+++ b/core/modules/config/config_test/config_test.module
@@ -0,0 +1,236 @@
+<?php
+
+use Drupal\Core\Config\ConfigException;
+use Drupal\config_test\ConfigTest;
+
+require_once dirname(__FILE__) . '/config_test.hooks.inc';
+
+/**
+ * Implements hook_config_import().
+ */
+function config_test_config_import($op, $name, $new_config, $old_config) {
+  // Set a global value we can check in test code.
+  $GLOBALS['hook_config_import'] = __FUNCTION__;
+
+  // Only configurable thingies require custom handling. Any other module
+  // settings can be synchronized directly.
+  if (strpos($name, 'config_test.dynamic.') !== 0) {
+    return FALSE;
+  }
+
+  if ($op == 'delete') {
+    $config_test = new ConfigTest($old_config);
+    $config_test->delete();
+  }
+  if ($op == 'create') {
+    $config_test = new ConfigTest($new_config);
+    $config_test->save();
+  }
+  if ($op == 'change') {
+    $config_test = new ConfigTest($new_config);
+    $config_test->setOriginal($old_config);
+    $config_test->save();
+  }
+  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);
+  // @todo Requires a more reliable + generic method to check for whether the
+  //   configuration object exists.
+  if ($config->get('id') === NULL) {
+    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['name'] = 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',
+    ),
+  );
+  $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/config_test/lib/Drupal/config_test/ConfigTest.php b/core/modules/config/config_test/lib/Drupal/config_test/ConfigTest.php
new file mode 100644
index 0000000..5d95d50
--- /dev/null
+++ b/core/modules/config/config_test/lib/Drupal/config_test/ConfigTest.php
@@ -0,0 +1,187 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\config_test\ConfigTest.
+ */
+
+namespace Drupal\config_test;
+
+/**
+ * Defines the ConfigTest configuration thingie.
+ *
+ * @todo At minimum, introduce a consistent interface for configurable thingies.
+ * @todo Consider to introduce a ConfigObjectBase for all configurable thingies.
+ */
+class ConfigTest {
+  /**
+   * The configuration object.
+   *
+   * @var Drupal\Core\Config\ConfigObject
+   */
+  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 = 'name';
+
+  /**
+   * The thingie's original ID, if any.
+   *
+   * @var string|null
+   */
+  public $originalId;
+
+  /**
+   * The original configuration object, if any, upon update.
+   *
+   * @var Drupal\Core\Config\ConfigObject
+   */
+  public $original;
+
+  // -- Custom properties start --
+
+  /**
+   * The image style to use.
+   *
+   * @var string
+   */
+  public $style;
+
+  // -- Custom properties end --
+
+  /**
+   * Constructs a new ConfigTest thingie.
+   */
+  public function __construct($config) {
+    $this->load($config);
+  }
+
+  /**
+   * Implements Drupal\Core\Config\ConfigObjectInterface::getConfigPrefix().
+   */
+  public function getConfigPrefix() {
+    return 'config_test.dynamic';
+  }
+
+  /**
+   * Implements ConfigObjectInterface::getName().
+   */
+  public function getConfigName() {
+    return $this->getConfigPrefix() . '.' . $this->getId;
+  }
+
+  /**
+   * Implements ConfigObjectInterface::getId().
+   */
+  public function getId() {
+    return $this->config->get($this->idKey);
+  }
+
+  /**
+   * Implements ConfigObjectInterface::isNew().
+   */
+  public function isNew() {
+    return isset($this->originalId);
+  }
+
+  /**
+   * Implements ConfigObjectInterface::getLabel().
+   */
+  public function getLabel() {
+    return $this->config->get($this->labelKey);
+  }
+
+  /**
+   * Implements ConfigObjectInterface::getUri().
+   */
+  public function getUri() {
+    // @todo Check whether this makes sense.
+    return 'admin/structure/config_test/manage/' . $this->getId();
+  }
+
+  /**
+   * Implements ConfigObjectInterface::get().
+   */
+  public function get($property_name, $langcode = NULL) {
+    return $this->config->get($property_name);
+  }
+
+  /**
+   * Implements ConfigObjectInterface::set().
+   */
+  public function set($property_name, $value, $langcode = NULL) {
+    return $this->config->set($property_name, $value);
+  }
+
+  /**
+   * Implements ConfigObjectInterface::setOriginal().
+   */
+  public function setOriginal($config) {
+    $this->original = $config;
+    $this->originalId = $config->get($this->idKey);
+    return $this;
+  }
+
+  /**
+   * Implements ConfigObjectInterface::load().
+   */
+  public function load($config) {
+    $this->config = $config;
+    $this->originalId = $config->get($this->idKey);
+    module_invoke_all('config_test_load', $this);
+    return $this;
+  }
+
+  /**
+   * Implements ConfigObjectInterface::save().
+   */
+  public function save() {
+    // Provide the original configuration in $config->original, if any.
+    if (isset($this->originalId)) {
+      $original_config = config($this->getConfigPrefix() . '.' . $this->originalId);
+      $this->setOriginal(new $this($original_config));
+    }
+
+    // Save the new configuration.
+    $this->config->setName($this->getConfigPrefix() . '.' . $this->getId());
+    module_invoke_all('config_test_presave', $this);
+    $this->config->save();
+
+    if (isset($this->originalId)) {
+      module_invoke_all('config_test_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.
+        $original_data = $original_config->get();
+        $original_config->delete();
+        $original_config->setData($original_data);
+      }
+    }
+    else {
+      module_invoke_all('config_test_insert', $this);
+    }
+
+    return $this;
+  }
+
+  /**
+   * Implements ConfigObjectInterface::delete().
+   */
+  public function delete() {
+    $this->config->delete();
+    module_invoke_all('config_test_delete', $this);
+  }
+}
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..68df5f1
--- /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,
+      'name' => '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(
+      'name' => '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,
+      'name' => '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,
+      'name' => '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
new file mode 100644
index 0000000..09baded
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php
@@ -0,0 +1,162 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\config\Tests\ConfigImportTestCase.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\Core\Config\DatabaseStorage;
+use Drupal\Core\Config\FileStorage;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests config_import() functionality.
+ */
+class ConfigImportTest extends WebTestBase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Import configuration',
+      'description' => 'Tests importing configuration from files into active store.',
+      'group' => 'Configuration',
+    );
+  }
+
+  function setUp() {
+    parent::setUp('config_test');
+  }
+
+  /**
+   * Tests deletion of configuration during import.
+   */
+  function testDeleted() {
+    $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');
+
+    // Export.
+    config_export();
+
+    // Delete the configuration objects.
+    $file_storage = new FileStorage();
+    $file_storage->delete($name);
+    $file_storage->delete($dynamic_name);
+
+    // Import.
+    config_import();
+
+    // Verify the values have disappeared.
+    $database_storage = new DatabaseStorage();
+    $this->assertIdentical($database_storage->read($name), array());
+    $this->assertIdentical($database_storage->read($dynamic_name), array());
+
+    $config = config($name);
+    $this->assertIdentical($config->get('foo'), NULL);
+    $config = config($dynamic_name);
+    $this->assertIdentical($config->get('id'), NULL);
+  }
+
+  /**
+   * Tests creation of configuration during import.
+   */
+  function testNew() {
+    $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.');
+
+    // Export.
+    config_export();
+
+    // Create new configuration objects.
+    $file_storage->write($name, array(
+      'add_me' => 'new value',
+    ));
+    $file_storage->write($dynamic_name, array(
+      'id' => 'new',
+      'name' => 'New',
+    ));
+    $this->assertIdentical($file_storage->exists($name), TRUE, $name . ' found.');
+    $this->assertIdentical($file_storage->exists($dynamic_name), TRUE, $dynamic_name . ' found.');
+
+    // Import.
+    config_import();
+
+    // Verify the values appeared.
+    $config = config($name);
+    $this->assertIdentical($config->get('add_me'), 'new value');
+    $config = config($dynamic_name);
+    $this->assertIdentical($config->get('name'), 'New');
+  }
+
+  /**
+   * Tests updating of configuration during import.
+   */
+  function testUpdated() {
+    $name = 'config_test.system';
+    $dynamic_name = 'config_test.dynamic.default';
+
+    // Export.
+    config_export();
+
+    // Replace the file content of the existing configuration objects.
+    $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(
+      'foo' => 'beer',
+    ));
+    $file_storage->write($dynamic_name, array(
+      'id' => 'default',
+      'name' => 'Updated',
+    ));
+
+    // Verify the active store still returns the default values.
+    $config = config($name);
+    $this->assertIdentical($config->get('foo'), 'bar');
+    $config = config($dynamic_name);
+    $this->assertIdentical($config->get('name'), 'Default');
+
+    // Import.
+    config_import();
+
+    // Verify the values were updated.
+    $config = config($name);
+    $this->assertIdentical($config->get('foo'), 'beer');
+    $config = config($dynamic_name);
+    $this->assertIdentical($config->get('name'), 'Updated');
+  }
+
+  /**
+   * Tests config_import() hook invocations.
+   */
+  function testSyncHooks() {
+    $name = 'config_test.system';
+    $dynamic_name = 'config_test.dynamic.default';
+
+    // Export.
+    config_export();
+
+    // Delete a file so that hook_config_import() hooks are run.
+    $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->delete($name);
+    $file_storage->delete($dynamic_name);
+
+    // Import.
+    config_import();
+
+    // Verify hook_config_import() was invoked.
+    $this->assertIdentical($GLOBALS['hook_config_import'], 'config_test_config_import');
+  }
+}
diff --git a/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php
new file mode 100644
index 0000000..8422d96
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigInstallTest.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\config\Tests\ConfigInstallTest.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests installation of configuration objects in installation functionality.
+ */
+class ConfigInstallTest extends WebTestBase {
+  public static function getInfo() {
+    return array(
+      'name' => 'Installation functionality',
+      'description' => 'Tests installation of configuration objects in installation functionality.',
+      'group' => 'Configuration',
+    );
+  }
+
+  /**
+   * Tests module installation.
+   */
+  function testModuleInstallation() {
+    $default_config = 'config_test.system';
+    $default_thingie = 'config_test.dynamic.default';
+
+    // Verify that default module config does not exist before installation yet.
+    // @todo Replace assertions with $config->exists() or ->isNew().
+    $config = config($default_config);
+    $this->assertIdentical($config->get(), array());
+    $config = config($default_thingie);
+    $this->assertIdentical($config->get(), array());
+
+    // Install the test module.
+    module_enable(array('config_test'));
+
+    // Verify that default module config exists.
+    // @todo Replace assertions with $config->exists() or ->isNew().
+    $config = config($default_config);
+    $this->assertNotIdentical($config->get(), array());
+    $config = config($default_thingie);
+    $this->assertNotIdentical($config->get(), array());
+
+    // 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($GLOBALS['hook_config_test_load']);
+    $this->assertTrue($GLOBALS['hook_config_test_presave']);
+    $this->assertTrue($GLOBALS['hook_config_test_insert']);
+    $this->assertFalse(isset($GLOBALS['hook_config_test_update']));
+    $this->assertFalse(isset($GLOBALS['hook_config_test_delete']));
+  }
+}
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index f3bf83d..28afd0b 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -500,6 +500,35 @@ function image_path_flush($path) {
 }
 
 /**
+ * Implements hook_config_import().
+ */
+function image_config_import($op, $name, $new_config, $old_config) {
+  // Only image styles require custom handling. Any other module settings can be
+  // synchronized directly.
+  if (strpos($name, 'image.style.') !== 0) {
+    return FALSE;
+  }
+
+  if ($op == 'delete') {
+    // @todo image_style_delete() supports the notion of a "replacement style"
+    //   to be used by other modules instead of the deleted style. Essential!
+    //   But that is impossible currently, since the config system only knows
+    //   about deleted and added changes. Introduce an 'old_ID' key within
+    //   config objects as a standard?
+    $style = $old_config->get();
+    return image_style_delete($style);
+  }
+  if ($op == 'create') {
+    $style = $new_config->get();
+    return image_style_save($style);
+  }
+  if ($op == 'change') {
+    $style = $new_config->get();
+    return image_style_save($style);
+  }
+}
+
+/**
  * Get an array of all styles and their settings.
  *
  * @return
