diff --git a/core/includes/config.inc b/core/includes/config.inc
index 8d0eea1..323cd95 100644
--- a/core/includes/config.inc
+++ b/core/includes/config.inc
@@ -2,6 +2,13 @@
 
 use Drupal\Core\Config\DatabaseStorage;
 use Drupal\Core\Config\FileStorage;
+use Drupal\Core\Config\ConfigException;
+
+/**
+ * The value a module should return from hook_config_sync() to indicate that
+ * it should be called again by the config system during this sync.
+ */
+const CONFIG_DEFER_SYNC = 'CONFIG_DEFER_SYNC';
 
 /**
  * @file
@@ -73,16 +80,218 @@ function config_get_storage_names_with_prefix($prefix = '') {
  *   The name of the configuration object to retrieve. The name corresponds to
  *   a configuration file. For @code config(book.admin) @endcode, the config
  *   object returned will contain the contents of book.admin configuration file.
- * @param $class
- *   The class name of the config object to be returned. Defaults to
- *   DrupalConfig.
+ * @param $config_class
+ *   (optional) The class name for the configuration object to be returned.
+ *   Defaults to Drupal\Core\Config\DupalConfig.
+ * @param $storage_class
+ *   (optional) A storage driver class name to use for the configuration class
+ *   object. Defaults to Drupal\Core\Config\DatabaseStorage.
  *
  * @return
- *   An instance of the class specified in the $class parameter.
+ *   An instance of the $config_class class.
  *
- * @todo Replace this with an appropriate factory / ability to inject in
- *   alternate storage engines..
+ * @todo Allow to inject alternate storage engines.
  */
-function config($name, $class = 'Drupal\Core\Config\DrupalConfig') {
-  return new $class(new DatabaseStorage($name));
+function config($name, $config_class = NULL, $storage_class = NULL) {
+  if (!isset($config_class)) {
+    $config_class = 'Drupal\Core\Config\DrupalConfig';
+  }
+  if (!isset($storage_class)) {
+    $storage_class = 'Drupal\Core\Config\DatabaseStorage';
+  }
+  return new $config_class(new $storage_class($name));
 }
+
+/**
+ * Synchronizes configuration from FileStorage to DatabaseStorage.
+ */
+function config_sync() {
+  $config_changes = config_get_changes_from_disk();
+  if (empty($config_changes)) {
+    return;
+  }
+
+  if (!lock_acquire(__FUNCTION__)) {
+    // Another request is synchronizing configuration. Wait for it to complete,
+    // then 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.
+    lock_wait(__FUNCTION__);
+    return FALSE;
+  }
+
+  try {
+    config_sync_invoke_sync_hooks($config_changes);
+    config_sync_save_changes($config_changes);
+    // Flush all caches and reset static variables after a successful import.
+    drupal_flush_all_caches();
+  }
+  catch (ConfigException $e) {
+    watchdog_exception('config_sync', $e);
+    config_sync_invoke_sync_error_hooks($config_changes);
+    lock_release(__FUNCTION__);
+    return FALSE;
+  }
+  lock_release(__FUNCTION__);
+  return TRUE;
+}
+
+/**
+ * Writes an array of config file changes to the active store.
+ *
+ * @param array $config_changes
+ *   An array of changes to be written.
+ */
+function config_sync_save_changes(array $config_changes) {
+  foreach (array('new', 'changed', 'deleted') as $type) {
+    foreach ($config_changes[$type] as $name) {
+      if ($type == 'deleted') {
+        config($name)->delete();
+      }
+      else {
+        // Get the active store object, set the new data from file, then save.
+        $target_config = config($name);
+        $source_config = config($name, NULL, 'Drupal\Core\Config\FileStorage');
+        $target_config->setData($source_config->get());
+        $target_config->save();
+      }
+    }
+  }
+}
+
+/**
+ * Invokes hook_config_sync_validate() and hook_config_sync() implementations.
+ *
+ * @param array $config_changes
+ *   An array of changes to be loaded.
+ */
+function config_sync_invoke_sync_hooks(array $config_changes) {
+  // Keep a copy of the changes so that modules cannot modify the values by
+  // taking the array by reference.
+  $config_changes_copy = $config_changes;
+
+  // @todo Lock writes to ALL config storages to prevent other/unintended config
+  //   changes from happening during the import.
+
+  $target_storage = config(NULL, NULL, 'Drupal\Core\Config\DatabaseStorage');
+  $source_storage = config(NULL, NULL, 'Drupal\Core\Config\FileStorage');
+
+  // Allow all modules to deny configuration changes.
+  foreach (module_implements('config_sync_validate') as $module) {
+    $config_changes = $config_changes_copy;
+    $function = $module . '_config_sync_validate';
+    $function($config_changes, $target_storage, $source_storage);
+  }
+
+  // We allow modules to signal that they would like to be rerun after all
+  // other modules by returning CONFIG_DEFER_SYNC. Loop until there are no
+  // modules left that indicate they would like to be re-run, checking that we
+  // are not stuck re-running the same list of modules infinitely.
+  // The list of modules is ordered by the reversed chain of module
+  // dependencies, in order to invoke dependent modules first, and thus decrease
+  // the possibility for CONFIG_DEFER_SYNC requests and increase the chance of
+  // being able to execute all changes in a single loop.
+  $modules = config_sync_sort_dependencies(module_implements('config_sync'));
+  do {
+    // Prevent an infinite loop. If the two variables stay the same, then all
+    // remaining modules asked to defer their import operations, which means
+    // that there is a unmet dependency.
+    $initial_module_list = $modules;
+
+    foreach ($modules as $key => $module) {
+      $config_changes = $config_changes_copy;
+      $function = $module . '_config_sync';
+      if ($function($config_changes, $target_storage, $source_storage) !== CONFIG_DEFER_SYNC) {
+        unset($modules[$key]);
+      }
+    }
+  } while ($modules && $modules != $initial_module_list);
+
+  // If there are modules left, then we hit an infinite loop.
+  if ($modules) {
+    throw new ConfigException('Unmet dependencies detected during synchronization.');
+  }
+}
+
+/**
+ * Invokes hook_config_sync_error() implementations.
+ *
+ * During a sync run, modules may make changes that cannot be rolled back.
+ * This hook allows modules to react to an error that occurs after they have
+ * made such changes, and make sure that the state of configuration in the
+ * active store is correct.
+ *
+ * @param array $config_changes
+ *   An array of changes to be loaded.
+ */
+function config_sync_invoke_sync_error_hooks(array $config_changes) {
+  $target_storage = config(NULL, NULL, 'Drupal\Core\Config\DatabaseStorage');
+  $source_storage = config(NULL, NULL, 'Drupal\Core\Config\FileStorage');
+
+  foreach (module_implements('config_sync_error') as $module) {
+    $function = $module . '_config_sync_error';
+    try {
+      $function($config_changes, $target_storage, $source_storage);
+    }
+    catch (ConfigException $e) {
+      // Just keep going, because we need to allow all modules to react even if
+      // some of them are behaving badly.
+    }
+  }
+}
+
+/**
+ * Sorts a given list of modules based on their dependencies.
+ *
+ * @param array $modules
+ *   A list of modules.
+ *
+ * @return array
+ *   The list of modules sorted by dependency.
+ */
+function config_sync_sort_dependencies(array $modules) {
+  // Get all module data so we can find weights and sort.
+  $module_data = system_rebuild_module_data();
+
+  $sorted_modules = array();
+  foreach ($modules as $module) {
+    $sorted_modules[$module] = $module_data[$module]->sort;
+  }
+  arsort($sorted_modules);
+  return array_keys($sorted_modules);
+}
+
+/**
+ * Returns a list of changes on disk compared to the active store.
+ *
+ * @return array|bool
+ *   The list of files changed on disk compared to the active store, or FALSE if
+ *   there are no differences.
+ */
+function config_get_changes_from_disk() {
+  $disk_config_names = FileStorage::getNamesWithPrefix();
+  $active_config_names = DatabaseStorage::getNamesWithPrefix();
+  $config_changes = array(
+    'new' => array_diff($disk_config_names, $active_config_names),
+    'changed' => array(),
+    'deleted' => array_diff($active_config_names, $disk_config_names),
+  );
+  foreach (array_intersect($disk_config_names, $active_config_names) as $name) {
+    $active_config = config($name);
+    $file_config = config($name, NULL, 'Drupal\Core\Config\FileStorage');
+    if ($active_config->get() != $file_config->get()) {
+      $config_changes['changed'][] = $name;
+    }
+  }
+
+  // Do not trigger subsequent synchronization operations if there are no
+  // changes in either category.
+  if (empty($config_changes['new']) && empty($config_changes['changed']) && empty($config_changes['deleted'])) {
+    return FALSE;
+  }
+
+  return $config_changes;
+}
+
diff --git a/core/includes/module.inc b/core/includes/module.inc
index 6b4604a..d916ea6 100644
--- a/core/includes/module.inc
+++ b/core/includes/module.inc
@@ -484,7 +484,7 @@ function module_enable($module_list, $enable_dependencies = TRUE) {
         $versions = drupal_get_schema_versions($module);
         $version = $versions ? max($versions) : SCHEMA_INSTALLED;
 
-        // Copy any default configuration data to the system config directory/
+        // Install default configuration of the module.
         config_install_default_config($module);
 
         // If the module has no current updates, but has some that were
diff --git a/core/lib/Drupal/Core/Config/DrupalConfig.php b/core/lib/Drupal/Core/Config/DrupalConfig.php
index f5a9220..c05417e 100644
--- a/core/lib/Drupal/Core/Config/DrupalConfig.php
+++ b/core/lib/Drupal/Core/Config/DrupalConfig.php
@@ -18,6 +18,13 @@ class DrupalConfig {
   protected $storage;
 
   /**
+   * The name of the current configuration object.
+   *
+   * @var string
+   */
+  protected $name;
+
+  /**
    * The data of the configuration object.
    *
    * @var array
@@ -34,7 +41,24 @@ class DrupalConfig {
    */
   public function __construct(StorageInterface $storage) {
     $this->storage = $storage;
+    // Retrieve the configuration object name assigned to the storage
+    // controller and automatically load it, if any.
+    $this->name = $this->storage->getName();
+    if (isset($this->name)) {
+      $this->read();
+    }
+  }
+
+  /**
+   * Loads a configuration object.
+   *
+   * @param string $name
+   *   The configuration object name to load.
+   */
+  public function load($name) {
+    $this->storage->setName($name);
     $this->read();
+    return $this;
   }
 
   /**
@@ -201,6 +225,7 @@ class DrupalConfig {
     else {
       drupal_array_unset_nested_value($this->data, $parts);
     }
+    return $this;
   }
 
   /**
@@ -208,6 +233,7 @@ class DrupalConfig {
    */
   public function save() {
     $this->storage->write($this->data);
+    return $this;
   }
 
   /**
@@ -216,5 +242,6 @@ class DrupalConfig {
   public function delete() {
     $this->data = array();
     $this->storage->delete();
+    return $this;
   }
 }
diff --git a/core/lib/Drupal/Core/Config/FileStorage.php b/core/lib/Drupal/Core/Config/FileStorage.php
index 5e2ac1e..f956eb5 100644
--- a/core/lib/Drupal/Core/Config/FileStorage.php
+++ b/core/lib/Drupal/Core/Config/FileStorage.php
@@ -2,15 +2,15 @@
 
 namespace Drupal\Core\Config;
 
+use Drupal\Core\Config\StorageInterface;
 use Symfony\Component\Yaml\Yaml;
 
 /**
  * Represents the file storage controller.
  *
- * @todo Implement StorageInterface after removing DrupalConfig methods.
  * @todo Consider to extend StorageBase.
  */
-class FileStorage {
+class FileStorage implements StorageInterface {
 
   /**
    * The name of the configuration object.
@@ -82,7 +82,7 @@ class FileStorage {
    * @return bool
    *   TRUE if the configuration file exists, FALSE otherwise.
    */
-  protected function exists() {
+  public function exists() {
     return file_exists($this->getFilePath());
   }
 
@@ -96,6 +96,7 @@ class FileStorage {
     if (!file_put_contents($this->getFilePath(), $data)) {
       throw new FileStorageException('Failed to write configuration file: ' . $this->getFilePath());
     }
+    return $this;
   }
 
   /**
@@ -120,8 +121,8 @@ class FileStorage {
    * Deletes a configuration file.
    */
   public function delete() {
-    // Needs error handling and etc.
-    @drupal_unlink($this->getFilePath());
+    // @todo Error handling.
+    return @drupal_unlink($this->getFilePath());
   }
 
   /**
@@ -169,4 +170,41 @@ class FileStorage {
     };
     return array_map($clean_name, $files);
   }
+
+  /**
+   * Implements StorageInterface::copyToFile().
+   */
+  public function copyToFile() {
+    // @todo Untangle StorageInterface.
+  }
+
+  /**
+   * Implements StorageInterface::copyFromFile().
+   */
+  public function copyFromFile() {
+    // @todo Untangle StorageInterface.
+  }
+
+  /**
+   * Implements StorageInterface::deleteFile().
+   */
+  public function deleteFile() {
+    // @todo Untangle StorageInterface.
+    return $this->delete();
+  }
+
+  /**
+   * Implements StorageInterface::writeToActive().
+   */
+  public function writeToActive($data) {
+    // @todo Untangle StorageInterface.
+  }
+
+  /**
+   * Implements StorageInterface::writeToFile().
+   */
+  public function writeToFile($data) {
+    // @todo Untangle StorageInterface.
+    return $this->write($data);
+  }
 }
diff --git a/core/lib/Drupal/Core/Config/StorageBase.php b/core/lib/Drupal/Core/Config/StorageBase.php
index b03ff27..f521332 100644
--- a/core/lib/Drupal/Core/Config/StorageBase.php
+++ b/core/lib/Drupal/Core/Config/StorageBase.php
@@ -76,13 +76,6 @@ abstract class StorageBase implements StorageInterface {
   }
 
   /**
-   * Implements StorageInterface::isOutOfSync().
-   */
-  public function isOutOfSync() {
-    return $this->read() !== $this->readFromFile();
-  }
-
-  /**
    * Implements StorageInterface::write().
    */
   public function write($data) {
diff --git a/core/lib/Drupal/Core/Config/StorageInterface.php b/core/lib/Drupal/Core/Config/StorageInterface.php
index 43141a5..103b382 100644
--- a/core/lib/Drupal/Core/Config/StorageInterface.php
+++ b/core/lib/Drupal/Core/Config/StorageInterface.php
@@ -41,15 +41,6 @@ interface StorageInterface {
   function deleteFile();
 
   /**
-   * Checks whether the file and the storage is in sync.
-   *
-   * @return
-   *   TRUE if the file and the storage contains the same data, FALSE
-   *   if not.
-   */
-  function isOutOfSync();
-
-  /**
    * Writes the configuration data into the active storage and the file.
    *
    * @param $data
diff --git a/core/modules/config/config.admin.inc b/core/modules/config/config.admin.inc
new file mode 100644
index 0000000..48aa5fa
--- /dev/null
+++ b/core/modules/config/config.admin.inc
@@ -0,0 +1,58 @@
+<?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_get_changes_from_disk();
+
+  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_sync()) {
+    drupal_set_message(t('The configuration was imported successfully.'));
+  }
+  else {
+    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..e645c39
--- /dev/null
+++ b/core/modules/config/config.api.php
@@ -0,0 +1,125 @@
+<?php
+
+/**
+ * @file
+ * Hooks provided by the Configuration module.
+ */
+
+/**
+ * @defgroup config_hooks Configuration system hooks
+ * @{
+ * @todo Overall description of the configuration system.
+ * @}
+ */
+
+/**
+ * Validate configuration changes before they are synchronized.
+ *
+ * @param array $config_changes
+ *   An associative array whose keys denote the configuration differences
+ *   ('new', 'changed', 'deleted') and whose values are arrays of configuration
+ *   object names.
+ * @param $target_storage
+ *   A configuration class acting on the target storage to which the new
+ *   configuration will be written. Use $target_storage->load() to load a
+ *   configuration object.
+ * @param $source_storage
+ *   A configuration class acting on the source storage from which configuration
+ *   differences were read. Use $target_storage->load() to load a configuration
+ *   object.
+ *
+ * @throws ConfigException
+ *   In case a configuration change cannot be allowed.
+ */
+function hook_config_sync_validate($config_changes, $target_storage, $source_storage) {
+  // Deny changes to our settings.
+  if (isset($config_changes['changed']['mymodule.locked'])) {
+    throw new ConfigException('MyModule settings cannot be changed.');
+  }
+}
+
+/**
+ * Synchronize configuration changes.
+ *
+ * This hook is invoked when configuration is synchronized between storages and
+ * allows all modules to react to new, deleted, and changed configuration. This
+ * hook is invoked before the new configuration is written to the target storage.
+ * Implementations of this hook only react to the differences. The new
+ * configuration itself is written by the configuration system.
+ *
+ * @param array $config_changes
+ *   An associative array whose keys denote the configuration differences
+ *   ('new', 'changed', 'deleted') and whose values are arrays of configuration
+ *   object names.
+ * @param $target_storage
+ *   A configuration class acting on the target storage to which the new
+ *   configuration will be written. Use $target_storage->load() to load a
+ *   configuration object.
+ * @param $source_storage
+ *   A configuration class acting on the source storage from which configuration
+ *   differences were read. Use $target_storage->load() to load a configuration
+ *   object.
+ *
+ * @return
+ *   Nothing on successful synchronization, or CONFIG_DEFER_SYNC if a change
+ *   cannot be synchronized yet adn depends on another module to execute first.
+ */
+function hook_config_sync($config_changes, $target_storage, $source_storage) {
+  foreach ($config_changes['new'] as $name) {
+    if (strpos($name, 'image.style.') === 0) {
+      // Load the new configuration data and inform other modules about it.
+      $style = $source_storage->load($name)->get();
+      $style['is_new'] = TRUE;
+      module_invoke_all('image_style_save', $style);
+    }
+  }
+  foreach ($config_changes['changed'] as $name) {
+    if (strpos($name, 'image.style.') === 0) {
+      // Load the new configuration data, inform other modules about the change,
+      // and perform any further actions that may be required for the change to
+      // take effect.
+      $style = $source_storage->load($name)->get();
+      $style['is_new'] = FALSE;
+      module_invoke_all('image_style_save', $style);
+      // Delete existing derivative images.
+      image_style_flush($style);
+    }
+  }
+  foreach ($config_changes['deleted'] as $name) {
+    if (strpos($name, 'image.style.') === 0) {
+      // The style has been deleted, so read the previous configuration from the
+      // old storage.
+      $style = $target_storage->load($name)->get();
+      module_invoke_all('image_style_delete', $style);
+      // Delete existing derivative images.
+      image_style_flush($style);
+    }
+  }
+}
+
+/**
+ * Validate configuration changes before they are saved to the active store.
+ *
+ * During synchronization of configuration, modules may make changes that cannot
+ * be rolled back. This hook allows modules to react to an error that occurs
+ * after they have made such changes, and make sure that the state of
+ * configuration is as correct as possible.
+ *
+ * @param array $config_changes
+ *   An associative array whose keys denote the configuration differences
+ *   ('new', 'changed', 'deleted') and whose values are arrays of configuration
+ *   object names.
+ * @param $target_storage
+ *   A configuration class acting on the target storage to which the new
+ *   configuration will be written. Use $target_storage->load() to load a
+ *   configuration object.
+ * @param $source_storage
+ *   A configuration class acting on the source storage from which configuration
+ *   differences were read. Use $target_storage->load() to load a configuration
+ *   object.
+ */
+function hook_config_sync_error($config_changes, $target_storage, $source_storage) {
+  // @todo Feasability and usage of this hook is still unclear, without having a
+  //   backup of $target_storage at hand.
+}
+
diff --git a/core/modules/config/config.module b/core/modules/config/config.module
index b3d9bbc..56c3bb8 100644
--- a/core/modules/config/config.module
+++ b/core/modules/config/config.module
@@ -1 +1,34 @@
 <?php
+
+/**
+ * @file
+ * Configuration system that lets administrators modify
+ * configuration of the site.
+ */
+
+/**
+ * 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 b/core/modules/config/config.test
index 257ab0f..bf18819 100644
--- a/core/modules/config/config.test
+++ b/core/modules/config/config.test
@@ -286,6 +286,144 @@ class ConfigFileContentTestCase extends WebTestBase {
   }
 }
 
+/**
+ * Tests config_sync() functionality.
+ */
+class ConfigImportTestCase 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');
+    $this->fileExtension = FileStorage::getFileExtension();
+  }
+
+  /**
+   * Tests deletion of configuration during import.
+   */
+  function testDeleted() {
+    $name = 'config_test.system';
+
+    // Verify the default configuration value exists.
+    $config = config($name);
+    $this->assertIdentical($config->get('foo'), 'bar');
+
+    // Delete the configuration object.
+    $file = new FileStorage($name);
+    $file->delete();
+
+    // Import.
+    config_sync();
+
+    // Verify the value has disappeared.
+    $config = config($name);
+    $this->assertIdentical($config->get('foo'), NULL);
+  }
+
+  /**
+   * Tests creation of configuration during import.
+   */
+  function testNew() {
+    $name = 'config_test.new';
+
+    // Verify the configuration to create does not exist yet.
+    $file = new FileStorage($name);
+    $this->assertIdentical($file->exists(), FALSE, $name . ' not found.');
+
+    // Create a new configuration object.
+    $file->write(array(
+      'add_me' => 'new value',
+    ));
+    $this->assertIdentical($file->exists(), TRUE, $name . ' found.');
+
+    // Import.
+    config_sync();
+
+    // Verify the value has appeared.
+    $config = config($name);
+    $this->assertIdentical($config->get('add_me'), 'new value');
+  }
+
+  /**
+   * Tests updating of configuration during import.
+   */
+  function testUpdated() {
+    $name = 'config_test.system';
+
+    // Replace the file content of the existing configuration object.
+    $file = new FileStorage($name);
+    $this->assertIdentical($file->exists(), TRUE, $name . ' found.');
+    $file->write(array(
+      'foo' => 'beer',
+    ));
+
+    // Verify the active store still returns the default value.
+    $config = config($name);
+    $this->assertIdentical($config->get('foo'), 'bar');
+
+    // Import.
+    config_sync();
+
+    // Verify the value was updated.
+    $config = config($name);
+    $this->assertIdentical($config->get('foo'), 'beer');
+  }
+
+  /**
+   * Tests config_sync() hook invocations.
+   */
+  function testSyncHooks() {
+    $name = 'config_test.system';
+
+    // Delete a file so that hook_config_sync() hooks are run.
+    $file = new FileStorage($name);
+    $this->assertIdentical($file->exists(), TRUE, $name . ' found.');
+    $file->delete();
+
+    // Make the test implementation throw an error during synchronization, so
+    // hook_config_sync_error() is also invoked.
+    $GLOBALS['config_sync_throw_error'] = TRUE;
+
+    // Import.
+    config_sync();
+
+    // Verify hook_config_sync() was invoked.
+    $this->assertIdentical($GLOBALS['hook_config_sync'], 'config_test_config_sync');
+    // Verify hook_config_sync_error() was invoked.
+    $this->assertIdentical($GLOBALS['hook_config_sync_validate'], 'config_test_config_sync_validate');
+    // Verify hook_config_sync_error() was invoked.
+    $this->assertIdentical($GLOBALS['hook_config_sync_error'], 'config_test_config_sync_error');
+  }
+
+  /**
+   * Tests abort of import upon validation error.
+   */
+  function testSyncValidationError() {
+    $name = 'config_test.system';
+
+    // Delete a file so that hook_config_sync() hooks are run.
+    $file = new FileStorage($name);
+    $this->assertIdentical($file->exists(), TRUE, $name . ' found.');
+    $file->delete();
+
+    // Make the test implementation throw a validation error, aborting the
+    // synchronization.
+    $GLOBALS['config_sync_validate_throw_error'] = TRUE;
+
+    // Import.
+    config_sync();
+
+    // Verify the active store was not updated.
+    $config = config($name);
+    $this->assertIdentical($config->get('foo'), 'bar');
+  }
+}
+
   /**
    * Tests configuration overriding from settings.php.
    */
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.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.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..88abf7c
--- /dev/null
+++ b/core/modules/config/config_test/config_test.module
@@ -0,0 +1,36 @@
+<?php
+
+use Drupal\Core\Config\ConfigException;
+
+/**
+ * Implements hook_config_sync_validate().
+ */
+function config_test_config_sync_validate($config_changes, $target_storage, $source_storage) {
+  if (!empty($GLOBALS['config_sync_validate_throw_error'])) {
+    throw new ConfigException('Configuration changes are invalid.');
+  }
+
+  // Set a global value we can check in test code.
+  $GLOBALS['hook_config_sync_validate'] = __FUNCTION__;
+}
+
+/**
+ * Implements hook_config_sync().
+ */
+function config_test_config_sync($config_changes, $target_storage, $source_storage) {
+  if (!empty($GLOBALS['config_sync_throw_error'])) {
+    throw new ConfigException('Error during synchronization.');
+  }
+
+  // Set a global value we can check in test code.
+  $GLOBALS['hook_config_sync'] = __FUNCTION__;
+}
+
+/**
+ * Implements hook_config_sync_error().
+ */
+function config_test_config_sync_error($config_changes, $target_storage, $source_storage) {
+  // Set a global value we can check in test code.
+  $GLOBALS['hook_config_sync_error'] = __FUNCTION__;
+}
+
diff --git a/core/modules/image/image.module b/core/modules/image/image.module
index 3edf83c..c159a47 100644
--- a/core/modules/image/image.module
+++ b/core/modules/image/image.module
@@ -495,6 +495,46 @@ function image_path_flush($path) {
 }
 
 /**
+ * Implements hook_config_sync().
+ */
+function image_config_sync($config_changes, $target_storage, $source_storage) {
+  foreach ($config_changes['new'] as $file_name) {
+    if (strpos($file_name, 'image.style.') === 0) {
+      $style = $source_storage->load($file_name)->get();
+      $style['is_new'] = TRUE;
+      module_invoke_all('image_style_save', $style);
+      image_style_flush($style);
+    }
+  }
+  foreach ($config_changes['changed'] as $file_name) {
+    if (strpos($file_name, 'image.style.') === 0) {
+      $style = $source_storage->load($file_name)->get();
+      $style['is_new'] = FALSE;
+      module_invoke_all('image_style_save', $style);
+      image_style_flush($style);
+    }
+  }
+  foreach ($config_changes['deleted'] as $file_name) {
+    if (strpos($file_name, 'image.style.') === 0) {
+      // The style has been deleted, so read the previous configuration from the
+      // old storage.
+      $style = $target_storage->load($file_name)->get();
+      image_style_flush($style);
+
+      // @todo image_style_delete() supports the notion of a "replacement style"
+      //   to be used by other modules instead of the deleted style. Good idea.
+      //   But squeezing that into a "delete" operation is the worst idea ever.
+      //   Regardless of Image module insanity, add a 'replaced' stack to
+      //   config_sync()? And how can that work? If an 'old_ID' key would be a
+      //   standard, wouldn't this belong into 'changed' instead?
+      $style['old_name'] = $style['name'];
+      $style['name'] = '';
+      module_invoke_all('image_style_delete', $style);
+    }
+  }
+}
+
+/**
  * Get an array of all styles and their settings.
  *
  * @return
