diff --git a/core/includes/config.inc b/core/includes/config.inc
index 51476b7..c143dd4 100644
--- a/core/includes/config.inc
+++ b/core/includes/config.inc
@@ -91,13 +91,13 @@ function config_sync_get_changes(StorageInterface $source_storage, StorageInterf
   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) {
+    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.
+  // changes in any category.
   if (empty($config_changes['create']) && empty($config_changes['change']) && empty($config_changes['delete'])) {
     return FALSE;
   }
@@ -129,6 +129,48 @@ function config_sync_changes(array $config_changes, StorageInterface $source_sto
 }
 
 /**
+ * Imports configuration from FileStorage to DatabaseStorage.
+ *
+ * @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.
+  $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 differentiate between
+    // an actual synchronization error and a failed lock, because concurrent
+    // synchronizations are an edge-case happening only when multiple developers
+    // or site builders attempt to do it without coordinating.
+    return FALSE;
+  }
+
+  $success = TRUE;
+  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);
+    $success = FALSE;
+  }
+  lock_release(__FUNCTION__);
+  return $success;
+}
+
+/**
  * Invokes MODULE_config_import() callbacks for configuration changes.
  *
  * @param array $config_changes
@@ -176,3 +218,20 @@ function config_import_invoke_owner(array $config_changes, StorageInterface $sou
   }
   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/modules/config/config.admin.inc b/core/modules/config/config.admin.inc
new file mode 100644
index 0000000..87a1463
--- /dev/null
+++ b/core/modules/config/config.admin.inc
@@ -0,0 +1,132 @@
+<?php
+
+/**
+ * @file
+ * Admin page callbacks for the config module.
+ */
+
+use Drupal\Core\Config\DatabaseStorage;
+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 DatabaseStorage.
+  // @todo Leverage DI + config.storage.info.
+  $source_storage = new FileStorage();
+  $target_storage = new DatabaseStorage();
+
+  // 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 DatabaseStorage and FileStorage.
+  // @todo Leverage DI + config.storage.info.
+  $source_storage = new DatabaseStorage();
+  $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.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/ConfigImportTest.php b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php
new file mode 100644
index 0000000..a6e72b4
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportTest.php
@@ -0,0 +1,138 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\config\Tests\ConfigImportTest.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\Core\Config\DatabaseStorage;
+use Drupal\Core\Config\FileStorage;
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Tests importing configuration from files into active store.
+ */
+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(array('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), FALSE);
+    $this->assertIdentical($database_storage->read($dynamic_name), FALSE);
+
+    $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',
+      'label' => '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('label'), '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',
+      'label' => '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('label'), '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('label'), 'Updated');
+  }
+}
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..cdd2704
--- /dev/null
+++ b/core/modules/config/lib/Drupal/config/Tests/ConfigImportUITest.php
@@ -0,0 +1,144 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\config\Tests\ConfigImportUITest.
+ */
+
+namespace Drupal\config\Tests;
+
+use Drupal\Core\Config\DatabaseStorage;
+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/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
