diff --git a/core/modules/aggregator/aggregator.module b/core/modules/aggregator/aggregator.module
index 35cdc19..2fd5929 100644
--- a/core/modules/aggregator/aggregator.module
+++ b/core/modules/aggregator/aggregator.module
@@ -371,11 +371,9 @@ function aggregator_save_category($edit) {
         ->condition('cid', $edit['cid'])
         ->execute();
       // Make sure there is no active block for this category.
-      $block_configs = config_get_storage_names_with_prefix('plugin.core.block');
-      foreach ($block_configs as $config_id) {
-        $config = config($config_id);
-        if ($config->get('id') == 'aggregator_category_block:' . $edit['cid']) {
-          $config->delete();
+      if (module_exists('block')) {
+        foreach (entity_load_multiple_by_properties('block', array('plugin' => 'aggregator_category_block:' . $edit['cid'])) as $block) {
+          $block->delete();
         }
       }
       $edit['title'] = '';
@@ -439,11 +437,9 @@ function aggregator_save_feed($edit) {
       ->condition('fid', $edit['fid'])
       ->execute();
     // Make sure there is no active block for this feed.
-    $block_configs = config_get_storage_names_with_prefix('plugin.core.block');
-    foreach ($block_configs as $config_id) {
-      $config = config($config_id);
-      if ($config->get('id') == 'aggregator_feed_block:' . $edit['fid']) {
-        $config->delete();
+    if (module_exists('block')) {
+      foreach (entity_load_multiple_by_properties('block', array('plugin' => 'aggregator_feed_block:' . $edit['fid'])) as $block) {
+        $block->delete();
       }
     }
   }
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorRenderingTest.php b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorRenderingTest.php
index 1f13232..b52aebf 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorRenderingTest.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Tests/AggregatorRenderingTest.php
@@ -52,14 +52,16 @@ public function testBlockLinks() {
     $this->drupalLogin($admin_user);
 
     $block = array(
-      'title' => 'feed-' . $feed->title,
-      'block_count' => 2,
+      'label' => 'feed-' . $feed->title,
+      'configuration' => array(
+        'block_count' => 2,
+      ),
     );
     $this->drupalPlaceBlock("aggregator_feed_block:{$feed->fid}", $block);
 
     // Confirm that the block is now being displayed on pages.
     $this->drupalGet('node');
-    $this->assertText(t($block['title']), 'Feed block is displayed on the page.');
+    $this->assertText(t($block['label']), 'Feed block is displayed on the page.');
 
     // Find the expected read_more link.
     $href = 'aggregator/sources/' . $feed->fid;
@@ -77,7 +79,7 @@ public function testBlockLinks() {
     aggregator_save_feed((array) $feed);
     // Check that the block is no longer displayed.
     $this->drupalGet('node');
-    $this->assertNoText(t($block['title']), 'Feed block is not displayed on the page when number of items is set to 0.');
+    $this->assertNoText(t($block['label']), 'Feed block is not displayed on the page when number of items is set to 0.');
   }
 
   /**
diff --git a/core/modules/block/block.admin.inc b/core/modules/block/block.admin.inc
index 13f17ad..2259176 100644
--- a/core/modules/block/block.admin.inc
+++ b/core/modules/block/block.admin.inc
@@ -5,6 +5,8 @@
  * Admin page callbacks for the block module.
  */
 
+use Drupal\block\Plugin\Core\Entity\Block;
+
 /**
  * Page callback: Attaches CSS for the block region demo.
  *
@@ -56,7 +58,7 @@ function block_admin_display_prepare_blocks($theme) {
   $blocks = _block_rehash($theme);
   $compare_theme = &drupal_static('_block_compare:theme');
   $compare_theme = $theme;
-  usort($blocks, '_block_compare');
+  uasort($blocks, '_block_compare');
   return $blocks;
 }
 
@@ -115,12 +117,7 @@ function block_admin_display_form($form, &$form_state, $blocks, $theme, $block_r
   $form['#tree'] = TRUE;
 
   foreach ($blocks as $key => $instance) {
-    $block = $instance->getConfig();
-    $form['blocks'][$key]['config_id'] = array(
-      '#type' => 'value',
-      '#value' => $block['config_id'],
-    );
-    $info = $instance->getDefinition();
+    $info = $instance->getPlugin()->getDefinition();
     $form['blocks'][$key]['info'] = array(
       '#markup' => check_plain($info['subject']),
     );
@@ -130,14 +127,14 @@ function block_admin_display_form($form, &$form_state, $blocks, $theme, $block_r
     );
     $form['blocks'][$key]['weight'] = array(
       '#type' => 'weight',
-      '#default_value' => $block['weight'],
+      '#default_value' => $instance->get('weight'),
       '#delta' => $weight_delta,
       '#title_display' => 'invisible',
       '#title' => t('Weight for @block block', array('@block' => $info['subject'])),
     );
     $form['blocks'][$key]['region'] = array(
       '#type' => 'select',
-      '#default_value' => $block['region'] != BLOCK_REGION_NONE ? $block['region'] : NULL,
+      '#default_value' => $instance->get('region') != BLOCK_REGION_NONE ? $instance->get('region') : NULL,
       '#empty_value' => BLOCK_REGION_NONE,
       '#title_display' => 'invisible',
       '#title' => t('Region for @block block', array('@block' => $info['subject'])),
@@ -145,11 +142,11 @@ function block_admin_display_form($form, &$form_state, $blocks, $theme, $block_r
     );
     $links['configure'] = array(
       'title' => t('configure'),
-      'href' => 'admin/structure/block/manage/' . $block['config_id'] . '/' . $theme . '/configure',
+      'href' => 'admin/structure/block/manage/' . $key . '/configure',
     );
     $links['delete'] = array(
       'title' => t('delete'),
-      'href' => 'admin/structure/block/manage/' . $block['config_id'] . '/' . $theme . '/delete',
+      'href' => 'admin/structure/block/manage/' . $key . '/delete',
     );
     $form['blocks'][$key]['operations'] = array(
       '#type' => 'operations',
@@ -180,11 +177,11 @@ function block_admin_display_form($form, &$form_state, $blocks, $theme, $block_r
  * @see block_admin_display_form()
  */
 function block_admin_display_form_submit($form, &$form_state) {
-  foreach ($form_state['values']['blocks'] as $block) {
-    $config = config($block['config_id']);
-    $config->set('weight', $block['weight']);
-    $config->set('region', $block['region']);
-    $config->save();
+  $blocks = entity_load_multiple('block', array_keys($form_state['values']['blocks']));
+  foreach ($blocks as $block_id => $block) {
+    $block->set('weight', $form_state['values']['blocks'][$block_id]['weight']);
+    $block->set('region', $form_state['values']['blocks'][$block_id]['region']);
+    $block->save();
   }
   drupal_set_message(t('The block settings have been updated.'));
   cache_invalidate_tags(array('content' => TRUE));
@@ -195,10 +192,8 @@ function block_admin_display_form_submit($form, &$form_state) {
  *
  * Callback for usort() in block_admin_display_prepare_blocks().
  */
-function _block_compare($ainstance, $binstance) {
+function _block_compare(Block $a, Block $b) {
   global $theme_key;
-  $a = $ainstance->getConfig();
-  $b = $binstance->getConfig();
 
   // Theme should be set before calling this function, or the current theme
   // is being used.
@@ -215,29 +210,31 @@ function _block_compare($ainstance, $binstance) {
   }
 
   // Separate enabled from disabled.
-  $status = $b['status'] - $a['status'];
+  $status = $b->get('status') - $a->get('status');
   if ($status) {
     return $status;
   }
   // Sort by region (in the order defined by theme .info file).
-  if ((!empty($a['region']) && !empty($b['region'])) && ($place = ($regions[$a['region']] - $regions[$b['region']]))) {
+  $aregion = $a->get('region');
+  $bregion = $b->get('region');
+  if ((!empty($aregion) && !empty($bregion)) && ($place = ($regions[$aregion] - $regions[$bregion]))) {
     return $place;
   }
   // Sort by weight, unless disabled.
-  if ($a['region'] != BLOCK_REGION_NONE) {
-    $weight = $a['weight'] - $b['weight'];
+  if ($a->get('region') != BLOCK_REGION_NONE) {
+    $weight = $a->get('weight') - $b->get('weight');
     if ($weight) {
       return $weight;
     }
   }
   // Sort by title.
-  $ainfo = $ainstance->getDefinition();
-  $binfo = $binstance->getDefinition();
+  $ainfo = $a->getPlugin()->getDefinition();
+  $binfo = $b->getPlugin()->getDefinition();
   return strcmp($ainfo['subject'], $binfo['subject']);
 }
 
 /**
- * Form constructor for the block instance configuration form.
+ * Page callback: Build the block instance add form.
  *
  * @param string $plugin_id
  *   The plugin ID for the block instance.
@@ -245,60 +242,47 @@ function _block_compare($ainstance, $binstance) {
  *   (optional) The name of the theme for the block instance. If no theme is
  *   specified, the default theme will be used.
  *
- * @see block_menu()
- * @see custom_block_menu()
- * @see block_admin_configure_validate()
- * @see block_admin_configure_submit()
- *
- * @ingroup forms
+ * @return array
+ *   The block instance edit form.
  */
-function block_admin_configure($form, &$form_state, $plugin_id, $theme = NULL) {
-  $instance = block_load($plugin_id);
-  $form['#instance'] = $instance;
-  $config = $instance->getConfig();
-  if (!isset($config['config_id']) && !$theme) {
-    $theme = variable_get('theme_default', 'stark');
-  }
-  elseif (!$theme && isset($config['config_id'])) {
-    list(, , , $theme) = explode('.', $config['config_id']);
-  }
-  $form['theme'] = array(
-    '#type' => 'value',
-    '#value' => $theme,
-  );
-  $form += $instance->form($form, $form_state);
-  return $form;
+function block_admin_add($plugin_id, $theme = NULL) {
+  $entity = entity_create('block', array(
+    'plugin' => $plugin_id,
+    'theme' => $theme,
+  ));
+  return entity_get_form($entity);
 }
 
 /**
- * Form validation handler for block_admin_configure().
+ * Page callback: Build the block instance edit form.
  *
- * @see block_admin_configure()
- * @see block_admin_configure_submit()
- */
-function block_admin_configure_validate($form, &$form_state) {
-  $form['#instance']->validate($form, $form_state);
-}
-
-/**
- * Form submission handler for block_admin_configure().
+ * @param string $entity_id
+ *   The entity ID for the block instance.
  *
- * @see block_admin_configure()
- * @see block_admin_configure_validate()
+ * @return array
+ *   The block instance edit form.
  */
-function block_admin_configure_submit($form, &$form_state) {
-  $form['#instance']->submit($form, $form_state);
-  $config_values = $form['#instance']->getConfig();
-  $machine_name = 'plugin.core.block.' . $form_state['values']['theme'] . '.' . $form_state['values']['machine_name'];
-  $config = config($machine_name);
-  $config->set('id', $form['#instance']->getPluginId());
-  foreach ($config_values as $key => $value) {
-    $config->set($key, $value);
+function block_admin_edit($entity_id) {
+  $entity = entity_load('block', $entity_id);
+
+  // Get the theme for the page title.
+  $admin_theme = config('system.theme')->get('admin');
+  $themes = list_themes();
+  $theme_key = $entity->get('theme');
+  $theme = $themes[$theme_key];
+  // Use meaningful titles for the main site and administrative themes.
+  $theme_title = $theme->info['name'];
+  if ($theme_key == variable_get('theme_default', 'stark')) {
+    $theme_title = t('!theme (default theme)', array('!theme' => $theme_title));
   }
-  $config->save();
-  drupal_set_message(t('The block configuration has been saved.'));
-  cache_invalidate_tags(array('content' => TRUE));
-  $form_state['redirect'] = 'admin/structure/block/list/block_plugin_ui:' . $form_state['values']['theme'];
+  elseif ($admin_theme && $theme_key == $admin_theme) {
+    $theme_title = t('!theme (administration theme)', array('!theme' => $theme_title));
+  }
+
+  // Get the block subject for the page title.
+  drupal_set_title(t("%label block in %theme", array('%label' => $entity->label(), '%theme' => $theme_title)), PASS_THROUGH);
+
+  return entity_get_form($entity);
 }
 
 /**
@@ -313,12 +297,10 @@ function block_admin_configure_submit($form, &$form_state) {
  * @see block_admin_block_delete_submit()
  */
 function block_admin_block_delete($form, &$form_state, $plugin_id, $theme) {
-  $block = block_load($plugin_id);
+  $block = entity_load('block', $plugin_id);
+  $subject = $block->label();
   $form['id'] = array('#type' => 'value', '#value' => $plugin_id);
   $form['theme'] = array('#type' => 'value', '#value' => $theme);
-  $definition = $block->getDefinition();
-  $config = $block->getConfig();
-  $subject = empty($config['subject']) ? $definition['subject'] : $config['subject'];
   $form['subject'] = array('#type' => 'value', '#value' => $subject);
 
   return confirm_form($form, t('Are you sure you want to delete the block %name?', array('%name' => $subject)), 'admin/structure/block', '', t('Delete'), t('Cancel'));
@@ -330,8 +312,8 @@ function block_admin_block_delete($form, &$form_state, $plugin_id, $theme) {
  * @see block_admin_block_delete()
  */
 function block_admin_block_delete_submit($form, &$form_state) {
-  $config = config($form_state['values']['id']);
-  $config->delete();
+  $block = entity_load('block', $form_state['values']['id']);
+  $block->delete();
   drupal_set_message(t('The block %name has been removed.', array('%name' => $form_state['values']['subject'])));
   $form_state['redirect'] = 'admin/structure/block/list/block_plugin_ui:' . $form_state['values']['theme'];
 }
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index a31feec..3763584 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -121,22 +121,29 @@ function block_menu() {
     'access arguments' => array('administer blocks'),
     'file' => 'block.admin.inc',
   );
-  $items['admin/structure/block/manage/%/%'] = array(
+  $items['admin/structure/block/add/%/%'] = array(
     'title' => 'Configure block',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('block_admin_configure', 4, 5),
+    'page callback' => 'block_admin_add',
+    'page arguments' => array(4, 5),
+    'access arguments' => array('administer blocks'),
+    'file' => 'block.admin.inc',
+  );
+  $items['admin/structure/block/manage/%'] = array(
+    'title' => 'Configure block',
+    'page callback' => 'block_admin_edit',
+    'page arguments' => array(4),
     'access arguments' => array('administer blocks'),
     'file' => 'block.admin.inc',
   );
-  $items['admin/structure/block/manage/%/%/configure'] = array(
+  $items['admin/structure/block/manage/%/configure'] = array(
     'title' => 'Configure block',
     'type' => MENU_DEFAULT_LOCAL_TASK,
     'context' => MENU_CONTEXT_INLINE,
   );
-  $items['admin/structure/block/manage/%/%/delete'] = array(
+  $items['admin/structure/block/manage/%/delete'] = array(
     'title' => 'Delete block',
     'page callback' => 'drupal_get_form',
-    'page arguments' => array('block_admin_block_delete', 4, 5),
+    'page arguments' => array('block_admin_block_delete', 4),
     'access arguments' => array('administer blocks'),
     'type' => MENU_LOCAL_TASK,
     'context' => MENU_CONTEXT_NONE,
@@ -306,27 +313,24 @@ function _block_get_renderable_region($list = array()) {
     !in_array($_SERVER['REQUEST_METHOD'], array('GET', 'HEAD'));
 
   foreach ($list as $key => $block) {
-    $config = $block->getConfig();
-    $definition = $block->getDefinition();
-    $build[$key] = array(
-      '#block' => $block,
-      '#weight' => (int) $config['weight'],
-      '#theme_wrappers' => array('block'),
-    );
-
-    if ($not_cacheable || in_array($config['cache'], array(DRUPAL_NO_CACHE, DRUPAL_CACHE_CUSTOM))) {
-      // Non-cached blocks get built immediately. Provides more content
-      // that can be easily manipulated during hook_page_alter().
-      $build[$key] = _block_get_renderable_block($build[$key]);
+
+    if ($not_cacheable || in_array($block->get('cache'), array(DRUPAL_NO_CACHE, DRUPAL_CACHE_CUSTOM))) {
+      // Non-cached blocks get built immediately.
+      if ($block->access()) {
+        $build[$key] = entity_view($block, 'block');
+      }
     }
     else {
       $key_components = explode('.', $key);
       $id = array_pop($key_components);
-      $build[$key] += array(
+      $build[$key] = array(
+        '#block' => $block,
+        '#weight' => $block->get('weight'),
+        '#theme_wrappers' => array('block'),
         '#pre_render' => array('_block_get_renderable_block'),
         '#cache' => array(
-          'keys' => array($id, $config['module']),
-          'granularity' => $config['cache'],
+          'keys' => array($id, $block->get('module')),
+          'granularity' => $block->get('cache'),
           'bin' => 'block',
           'tags' => array('content' => TRUE),
         ),
@@ -338,9 +342,8 @@ function _block_get_renderable_region($list = array()) {
     // skip the help block, since we assume that most users do not need or want
     // to perform contextual actions on the help block, and the links needlessly
     // draw attention on it.
-    if ($definition['class'] != 'Drupal\\system\\Plugin\\block\\block\\SystemHelpBlock' && $definition['class'] != 'Drupal\\system\\Plugin\\block\\block\\SystemMainBlock') {
-      global $theme;
-      $build[$key]['#contextual_links']['block'] = array('admin/structure/block/manage', array($key, $theme));
+    if (!in_array($block->get('plugin'), array('system_help_block', 'system_main_block'))) {
+      $build[$key]['#contextual_links']['block'] = array('admin/structure/block/manage', array($key));
     }
   }
   return $build;
@@ -357,33 +360,25 @@ function _block_get_renderable_region($list = array()) {
  *   Blocks currently exported by modules.
  */
 function _block_rehash($theme = NULL) {
-  $blocks = array();
-  $instances = array();
   $theme = $theme ? $theme : variable_get('theme_default', 'stark');
-  $block_configs = config_get_storage_names_with_prefix('plugin.core.block.' . $theme);
   $regions = system_region_list($theme);
-  foreach ($block_configs as $config) {
-    // Only list valid block instances.
-    if (!$block = block_load($config)) {
-      continue;
-    }
-    $blocks[$config] = $block;
-    $config = config($config);
-    $region = $config->get('region');
-    $status = $config->get('status');
+  $blocks = entity_load_multiple_by_properties('block', array('theme' => $theme));
+  foreach ($blocks as $block_id => $block) {
+    $region = $block->get('region');
+    $status = $block->get('status');
     // Disable blocks in invalid regions.
     if (!empty($region) && $region != BLOCK_REGION_NONE && !isset($regions[$region]) && $status == 1) {
-      drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $config->get('id'), '%region' => $region)), 'warning');
+      drupal_set_message(t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $block_id, '%region' => $region)), 'warning');
       // Disabled modules are moved into the BLOCK_REGION_NONE later so no
       // need to move the block to another region.
-      $config->set('status', 0);
-      $config->save();
+      $block->set('status', 0);
+      $block->save();
     }
     // Set region to none if not enabled and make sure status is set.
     if (empty($status)) {
-      $config->set('region', BLOCK_REGION_NONE);
-      $config->set('status', 0);
-      $config->save();
+      $block->set('region', BLOCK_REGION_NONE);
+      $block->set('status', 0);
+      $block->save();
     }
   }
   return $blocks;
@@ -414,28 +409,23 @@ function block_themes_enabled($theme_list) {
  */
 function block_theme_initialize($theme) {
   // Initialize theme's blocks if none already registered.
-  $has_blocks = config_get_storage_names_with_prefix('plugin.core.block.' . $theme);
+  $has_blocks = entity_load_multiple_by_properties('block', array('theme' => $theme));
   if (!$has_blocks) {
     $default_theme = variable_get('theme_default', 'stark');
     // Apply only to new theme's visible regions.
     $regions = system_region_list($theme, REGIONS_VISIBLE);
-    $default_theme_blocks = config_get_storage_names_with_prefix('plugin.core.block.' . $default_theme);
-    foreach ($default_theme_blocks as $config_id) {
-      $block_config = config($config_id)->get();
-      $machine_name = explode('.', $config_id);
-      $machine_name = array_pop($machine_name);
-      $new_config_id = 'plugin.core.block.' . $theme . '.' . $machine_name;
-      $new_block = config($new_config_id);
+    $default_theme_blocks = entity_load_multiple_by_properties('block', array('theme' => $default_theme));
+    foreach ($default_theme_blocks as $default_theme_block_id => $default_theme_block) {
+      list(, $machine_name) = explode('.', $default_theme_block_id);
+      $block = $default_theme_block->createDuplicate();
+      $block->set('id', $theme . '.' . $machine_name);
+      $block->set('theme', $theme);
       // If the region isn't supported by the theme, assign the block to the
       // theme's default region.
-      if (!isset($regions[$block_config['region']])) {
-        $new_block->set('region', system_default_region($theme));
-        unset($block_config['region']);
-      }
-      foreach ($block_config as $key => $value) {
-        $new_block->set($key, $value);
+      if (!isset($regions[$block->get('region')])) {
+        $block->set('region', system_default_region($theme));
       }
-      $new_block->save();
+      $block->save();
     }
   }
 }
@@ -456,7 +446,11 @@ function block_list($region) {
   $blocks = &drupal_static(__FUNCTION__);
 
   if (!isset($blocks)) {
-    $blocks = _block_load_blocks();
+    global $theme;
+    $blocks = array();
+    foreach (entity_load_multiple_by_properties('block', array('theme' => $theme)) as $block_id => $block) {
+      $blocks[$block->get('region')][$block_id] = $block;
+    }
   }
 
   // Create an empty array if there are no entries.
@@ -468,62 +462,6 @@ function block_list($region) {
 }
 
 /**
- * Loads a block instance.
- *
- * @param string $plugin_id
- *   The plugin ID to load.
- * @param array $conf
- *   An optional configuration array for creating a block instance manually
- *   rather than retrieving it from the configuration system.
- *
- * @return
- *   A block object.
- *
- * @todo Add block_load_multiple() and make this function a single-value wrapper.
- */
-function block_load($plugin_id, array $conf = array()) {
-  $manager = drupal_container()->get('plugin.manager.block');
-  if (!$block = $manager->getInstance(array('config' => $plugin_id))) {
-    // If the block instance does not exist, try to create it.
-    try {
-      $block = $manager->createInstance($plugin_id, $conf);
-    }
-    catch (PluginException $e) {
-      $config = config($plugin_id)->get();
-      // Ignore blocks belonging to disabled modules, but re-throw valid
-      // exceptions when the module is enabled and the plugin is misconfigured.
-      if (empty($config['module']) || module_exists($config['module'])) {
-        throw $e;
-      }
-      return FALSE;
-    }
-  }
-  return $block;
-}
-
-/**
- * Loads blocks' information from the configuration management system.
- *
- * @return
- *   An array of blocks grouped by region.
- *
- * @todo Remove this function, and replace it with a block_load_multiple().
- */
-function _block_load_blocks() {
-  global $theme;
-  $blocks = array();
-  $instances = config_get_storage_names_with_prefix('plugin.core.block.' . $theme);
-  $manager = drupal_container()->get('plugin.manager.block');
-  foreach ($instances as $plugin_id) {
-    if ($block = block_load($plugin_id)) {
-      $config = $block->getConfig();
-      $blocks[$config['region']]["$plugin_id"] = $block;
-    }
-  }
-  return $blocks;
-}
-
-/**
  * Builds the content and subject for a block.
  *
  * For cacheable blocks, this is called during #pre_render.
@@ -538,17 +476,7 @@ function _block_get_renderable_block($element) {
   $block = $element['#block'];
   // Don't bother to build blocks that aren't accessible.
   if ($element['#access'] = $block->access()) {
-    $build = $block->build();
-    if ($build) {
-      if (isset($build['#title'])) {
-        $element['#title'] = $build['#title'];
-      }
-      $element += $build;
-    }
-    else {
-      // @todo Add an inline comment explaining why this line is necessary.
-      $element = array();
-    }
+    $element += entity_view($block, 'block');
   }
   return $element;
 }
@@ -590,10 +518,9 @@ function block_rebuild() {
  */
 function template_preprocess_block(&$variables) {
   $block_counter = &drupal_static(__FUNCTION__, array());
-  $variables['block'] = (object) array_merge($variables['elements']['#block']->getDefinition(), $variables['elements']['#block']->getConfig());
-  if (!empty($variables['elements']['#title']) && empty($variables['block']->subject)) {
-    $variables['block']->subject = $variables['elements']['#title'];
-  }
+
+  $variables['block'] = (object) $variables['elements']['#block_config'];
+
   // All blocks get an independent counter for each region.
   if (!isset($block_counter[$variables['block']->region])) {
     $block_counter[$variables['block']->region] = 1;
@@ -623,14 +550,14 @@ function template_preprocess_block(&$variables) {
 
   // We can safely explode on : because we know the Block plugin type manager
   // enforces that delimiter for all derivatives.
-  $parts = explode(':', $variables['elements']['#block']->getPluginId());
+  $parts = explode(':', $variables['elements']['#block']->get('plugin'));
   $suggestion = 'block';
   while ($part = array_shift($parts)) {
     $variables['theme_hook_suggestions'][] = $suggestion .= '__' . strtr($part, '-', '_');
   }
   // Create a valid HTML ID and make sure it is unique.
-  if (!empty($variables['block']->config_id)) {
-    $config_id = explode('.', $variables['block']->config_id);
+  if ($id = $variables['elements']['#block']->id()) {
+    $config_id = explode('.', $id);
     $machine_name = array_pop($config_id);
     $variables['block_html_id'] = drupal_html_id('block-' . $machine_name);
     $variables['theme_hook_suggestions'][] = 'block__' . $machine_name;
@@ -643,14 +570,12 @@ function template_preprocess_block(&$variables) {
  * Removes deleted role from blocks that use it.
  */
 function block_user_role_delete($role) {
-  $block_configs = config_get_storage_names_with_prefix('plugin.core.block');
-  foreach ($block_configs as $config_id) {
-    $config = config($config_id);
-    $roles = $config->get('visibility.role.roles');
-    if (isset($roles[$role->rid])) {
-      unset($roles[$role->rid]);
-      $config->set('visibility.role.roles', $roles);
-      $config->save();
+  foreach (entity_load_multiple('block') as $block_id => $block) {
+    $visibility = $block->get('visibility');
+    if (isset($visibility['roles']['roles'][$role->rid])) {
+      unset($visibility['roles']['roles'][$role->rid]);
+      $block->set('visibility', $visibility);
+      $block->save();
     }
   }
 }
@@ -659,11 +584,9 @@ function block_user_role_delete($role) {
  * Implements hook_menu_delete().
  */
 function block_menu_delete($menu) {
-  $block_configs = config_get_storage_names_with_prefix('plugin.core.block');
-  foreach ($block_configs as $config_id) {
-    $config = config($config_id);
-    if ($config->get('id') == 'menu_menu_block:' . $menu['menu_name']) {
-      $config->delete();
+  foreach (entity_load_multiple('block') as $block_id => $block) {
+    if ($block->get('plugin') == 'menu_menu_block:' . $menu['menu_name']) {
+      $block->delete();
     }
   }
 }
@@ -681,35 +604,18 @@ function block_admin_paths() {
 }
 
 /**
- * Implements hook_modules_uninstalled().
- *
- * Cleans up any block configuration for uninstalled modules.
- */
-function block_modules_uninstalled($modules) {
-  $block_configs = config_get_storage_names_with_prefix('plugin.core.block');
-  foreach ($block_configs as $config_id) {
-    $config = config($config_id);
-    if (in_array($config->get('module'), $modules)) {
-      $config->delete();
-    }
-  }
-}
-
-/**
  * Implements hook_language_delete().
  *
  * Delete the potential block visibility settings of the deleted language.
  */
 function block_language_delete($language) {
   // Remove the block visibility settings for the deleted language.
-  $block_configs = config_get_storage_names_with_prefix('plugin.core.block');
-  foreach ($block_configs as $config_id) {
-    $config = config($config_id);
-    $languages = $config->get('visibility.language.langcodes');
-    if (isset($languages[$language->langcode])) {
-      unset($languages[$language->langcode]);
-      $config->set('visibility.language.langcodes', $languages);
-      $config->save();
+  foreach (entity_load_multiple('block') as $block_id => $block) {
+    $visibility = $block->get('visibility');
+    if (isset($visibility['language']['langcodes'][$language->langcode])) {
+      unset($visibility['language']['langcodes'][$language->langcode]);
+      $block->set('visibility', $visibility);
+      $block->save();
     }
   }
 }
diff --git a/core/modules/block/custom_block/custom_block.module b/core/modules/block/custom_block/custom_block.module
index d3e1588..8c7fdea 100644
--- a/core/modules/block/custom_block/custom_block.module
+++ b/core/modules/block/custom_block/custom_block.module
@@ -19,8 +19,8 @@ function custom_block_menu() {
       $items['admin/structure/block/list/' . $plugin_id . '/add/custom_blocks'] = array(
         'title' => 'Add custom block',
         'description' => 'Create a block with custom content and settings.',
-        'page callback' => 'drupal_get_form',
-        'page arguments' => array('block_admin_configure', 'custom_block:custom_block', $theme),
+        'page callback' => 'block_admin_add',
+        'page arguments' => array('custom_block:custom_block', $theme),
         'access callback' => TRUE,
         'type' => MENU_LOCAL_ACTION,
         'file' => 'block.admin.inc',
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlock.php
index 8b142ef..37b11d4 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlock.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlock.php
@@ -29,17 +29,6 @@
 class CustomBlock extends BlockBase {
 
   /**
-   * Overrides \Drupal\block\BlockBase::blockSettings().
-   */
-  public function blockSettings() {
-    // By default, use a blank block title rather than the block description
-    // (which is "Custom Block").
-    return array(
-      'subject' => '',
-    );
-  }
-
-  /**
    * Overrides \Drupal\block\BlockBase::getConfig().
    */
   public function getConfig() {
@@ -60,7 +49,7 @@ public function getConfig() {
   public function blockForm($form, &$form_state) {
     // @todo Disable this field when editing an existing block and provide a
     //   separate interface for administering custom blocks.
-    $form['custom_block']['info'] = array(
+    $form['info'] = array(
       '#type' => 'textfield',
       '#title' => t('Block description'),
       '#required' => TRUE,
@@ -69,7 +58,7 @@ public function blockForm($form, &$form_state) {
     );
     // @todo Disable this field when editing an existing block and provide a
     //   separate interface for administering custom blocks.
-    $form['custom_block']['body'] = array(
+    $form['body'] = array(
       '#type' => 'text_format',
       '#title' => t('Block body'),
       '#default_value' => $this->configuration['body'],
@@ -78,11 +67,24 @@ public function blockForm($form, &$form_state) {
       '#rows' => 15,
       '#required' => TRUE,
     );
-    $form['custom_block']['title']['#description'] = t('The title of the block as shown to the user.');
+    $form['title']['#description'] = t('The title of the block as shown to the user.');
     return $form;
   }
 
   /**
+   * Overrides \Drupal\block\BlockBase::blockValidate().
+   */
+  public function blockValidate($form, &$form_state) {
+    $custom_block_exists = (bool) db_query_range('SELECT 1 FROM {block_custom} WHERE bid <> :bid AND info = :info', 0, 1, array(
+      ':bid' => $form_state['values']['delta'],
+      ':info' => $form_state['values']['info'],
+    ))->fetchField();
+    if (empty($form_state['values']['info']) || $custom_block_exists) {
+      form_set_error('info', t('Ensure that each block description is unique.'));
+    }
+  }
+
+  /**
    * Overrides \Drupal\block\BlockBase::blockSubmit().
    */
   public function blockSubmit($form, &$form_state) {
@@ -94,7 +96,7 @@ public function blockSubmit($form, &$form_state) {
       'bid' => is_numeric($bid) ? $bid : NULL,
     );
     drupal_write_record('block_custom', $block, !is_null($block['bid']) ? array('bid') : array());
-    $this->configuration['id'] = 'custom_block:' . $block['bid'];
+    $form_state['entity']->set('plugin', 'custom_block:' . $block['bid']);
     // Invalidate the block cache to update custom block-based derivatives.
     if (module_exists('block')) {
       drupal_container()->get('plugin.manager.block')->clearCachedDefinitions();
diff --git a/core/modules/block/lib/Drupal/block/BlockAccessController.php b/core/modules/block/lib/Drupal/block/BlockAccessController.php
new file mode 100644
index 0000000..0c19a4a
--- /dev/null
+++ b/core/modules/block/lib/Drupal/block/BlockAccessController.php
@@ -0,0 +1,105 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\BlockAccessController.
+ */
+
+namespace Drupal\block;
+
+use Drupal\Core\Entity\EntityAccessController;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\user\Plugin\Core\Entity\User;
+
+/**
+ * Provides a Block access controller.
+ */
+class BlockAccessController extends EntityAccessController {
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityAccessController::viewAccess().
+   */
+  public function viewAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
+    // If the block-specific access restrictions indicate the block is not
+    // accessible, always deny access.
+    if (!$entity->getPlugin()->blockAccess()) {
+      return FALSE;
+    }
+
+    // Otherwise, check for other access restrictions.
+    if (!$account) {
+      global $user;
+      $account = $user;
+    }
+
+    // Deny access to disabled blocks.
+    if (!$entity->get('status')) {
+      return FALSE;
+    }
+
+    // User role access handling.
+    // If a block has no roles associated, it is displayed for every role.
+    // For blocks with roles associated, if none of the user's roles matches
+    // the settings from this block, access is denied.
+    $visibility = $entity->get('visibility');
+    if (!empty($visibility['role']['roles']) && !array_intersect(array_filter($visibility['role']['roles']), array_keys($account->roles))) {
+      // No match.
+      return FALSE;
+    }
+
+    // Page path handling.
+    // Limited visibility blocks must list at least one page.
+    if (!empty($visibility['path']['visibility']) && $visibility['path']['visibility'] == BLOCK_VISIBILITY_LISTED && empty($visibility['path']['pages'])) {
+      return FALSE;
+    }
+
+    // Match path if necessary.
+    if (!empty($visibility['path']['pages'])) {
+      // Assume there are no matches until one is found.
+      $page_match = FALSE;
+
+      // Convert path to lowercase. This allows comparison of the same path
+      // with different case. Ex: /Page, /page, /PAGE.
+      $pages = drupal_strtolower($visibility['path']['pages']);
+      if ($visibility['path']['visibility'] < BLOCK_VISIBILITY_PHP) {
+        // Compare the lowercase path alias (if any) and internal path.
+        $path = current_path();
+        $path_alias = drupal_strtolower(drupal_container()->get('path.alias_manager')->getPathAlias($path));
+        $page_match = drupal_match_path($path_alias, $pages) || (($path != $path_alias) && drupal_match_path($path, $pages));
+        // When $block->visibility has a value of 0
+        // (BLOCK_VISIBILITY_NOTLISTED), the block is displayed on all pages
+        // except those listed in $block->pages. When set to 1
+        // (BLOCK_VISIBILITY_LISTED), it is displayed only on those pages
+        // listed in $block->pages.
+        $page_match = !($visibility['path']['visibility'] xor $page_match);
+      }
+      elseif (module_exists('php')) {
+        $page_match = php_eval($visibility['path']['pages']);
+      }
+
+      // If there are page visibility restrictions and this page does not
+      // match, deny access.
+      if (!$page_match) {
+        return FALSE;
+      }
+    }
+
+    // Language visibility settings.
+    if (!empty($visibility['language']['langcodes']) && array_filter($visibility['language']['langcodes'])) {
+      if (empty($visibility['language']['langcodes'][language($visibility['language']['language_type'])->langcode])) {
+        return FALSE;
+      }
+    }
+
+    // Check other modules for block access rules.
+    foreach (module_implements('block_access') as $module) {
+      if (module_invoke($module, 'block_access', $entity) === FALSE) {
+        return FALSE;
+      }
+    }
+
+    // If nothing denied access to the block, it is accessible.
+    return TRUE;
+  }
+
+}
diff --git a/core/modules/block/lib/Drupal/block/BlockBase.php b/core/modules/block/lib/Drupal/block/BlockBase.php
index b8f770d..b9a149f 100644
--- a/core/modules/block/lib/Drupal/block/BlockBase.php
+++ b/core/modules/block/lib/Drupal/block/BlockBase.php
@@ -19,25 +19,6 @@
 abstract class BlockBase extends PluginBase implements BlockInterface {
 
   /**
-   * Implements \Drupal\block\BlockInterface::settings().
-   *
-   * Most block plugins should not override this method. To add additional
-   * settings or change the default values for setting, override
-   * BlockBase::blockSettings().
-   *
-   * @see \Drupal\block\BlockBase::blockSettings()
-   */
-  public function settings() {
-    $settings = $this->blockSettings();
-    // By default, blocks are enabled and not cached.
-    $settings += array(
-      'status' => TRUE,
-      'cache' => DRUPAL_NO_CACHE,
-    );
-    return $settings;
-  }
-
-  /**
    * Returns plugin-specific settings for the block.
    *
    * Block plugins only need to override this method if they override the
@@ -69,7 +50,7 @@ public function getConfig() {
     if (empty($this->configuration)) {
       // If the plugin configuration is not already set, initialize it with the
       // default settings for the block plugin.
-      $this->configuration = $this->settings();
+      $this->configuration = $this->blockSettings();
 
       // @todo This loads the default subject. Is this the right place to do so?
       $definition = $this->getDefinition();
@@ -118,313 +99,6 @@ public function blockAccess() {
   }
 
   /**
-   * Implements \Drupal\block\BlockInterface::access().
-   *
-   * Adds the user-configured per-role, per-path, and per-language visibility
-   * settings to all blocks, and invokes hook_block_access().
-   *
-   * Most plugins should not override this method unless they need to remove
-   * the user-defined access restrictions. To add specific access
-   * restrictions for a particular block type, override
-   * BlockBase::blockAccess() instead.
-   *
-   * @see hook_block_access()
-   * @see \Drupal\block\BlockBase::blockAccess()
-   */
-  public function access() {
-    // If the block-specific access restrictions indicate the block is not
-    // accessible, always deny access.
-    if (!$this->blockAccess()) {
-      return FALSE;
-    }
-
-    // Otherwise, check for other access restrictions.
-    global $user;
-
-    // Deny access to disabled blocks.
-    if (empty($this->configuration['status'])) {
-      return FALSE;
-    }
-
-    // User role access handling.
-    // If a block has no roles associated, it is displayed for every role.
-    // For blocks with roles associated, if none of the user's roles matches
-    // the settings from this block, access is denied.
-    if (!empty($this->configuration['visibility']['role']['roles']) && !array_intersect(array_filter($this->configuration['visibility']['role']['roles']), array_keys($user->roles))) {
-      // No match.
-      return FALSE;
-    }
-
-    // Page path handling.
-    // Limited visibility blocks must list at least one page.
-    if (!empty($this->configuration['visibility']['path']['visibility']) && $this->configuration['visibility']['path']['visibility'] == BLOCK_VISIBILITY_LISTED && empty($this->configuration['visibility']['path']['pages'])) {
-      return FALSE;
-    }
-
-    // Match path if necessary.
-    if (!empty($this->configuration['visibility']['path']['pages'])) {
-      // Assume there are no matches until one is found.
-      $page_match = FALSE;
-
-      // Convert path to lowercase. This allows comparison of the same path
-      // with different case. Ex: /Page, /page, /PAGE.
-      $pages = drupal_strtolower($this->configuration['visibility']['path']['pages']);
-      if ($this->configuration['visibility']['path']['visibility'] < BLOCK_VISIBILITY_PHP) {
-        // Compare the lowercase path alias (if any) and internal path.
-        $path = current_path();
-        $path_alias = drupal_strtolower(drupal_container()->get('path.alias_manager')->getPathAlias($path));
-        $page_match = drupal_match_path($path_alias, $pages) || (($path != $path_alias) && drupal_match_path($path, $pages));
-        // When $block->visibility has a value of 0
-        // (BLOCK_VISIBILITY_NOTLISTED), the block is displayed on all pages
-        // except those listed in $block->pages. When set to 1
-        // (BLOCK_VISIBILITY_LISTED), it is displayed only on those pages
-        // listed in $block->pages.
-        $page_match = !($this->configuration['visibility']['path']['visibility'] xor $page_match);
-      }
-      elseif (module_exists('php')) {
-        $page_match = php_eval($this->configuration['visibility']['path']['pages']);
-      }
-
-      // If there are page visibility restrictions and this page does not
-      // match, deny access.
-      if (!$page_match) {
-        return FALSE;
-      }
-    }
-
-    // Language visibility settings.
-    if (!empty($this->configuration['visibility']['language']['langcodes']) && array_filter($this->configuration['visibility']['language']['langcodes'])) {
-      if (empty($this->configuration['visibility']['language']['langcodes'][language($this->configuration['visibility']['language']['language_type'])->langcode])) {
-        return FALSE;
-      }
-    }
-
-    // Check other modules for block access rules.
-    foreach (module_implements('block_access') as $module) {
-      if (module_invoke($module, 'block_access', $this) === FALSE) {
-        return FALSE;
-      }
-    }
-
-    // If nothing denied access to the block, it is accessible.
-    return TRUE;
-  }
-
-  /**
-   * Implements \Drupal\block\BlockInterface::form().
-   *
-   * Creates a generic configuration form for all block types. Individual
-   * block plugins can add elements to this form by overriding
-   * BlockBase::blockForm(). Most block plugins should not override this
-   * method unless they need to alter the generic form elements.
-   *
-   * @see \Drupal\block\BlockBase::blockForm()
-   */
-  public function form($form, &$form_state) {
-    $definition = $this->getDefinition();
-    $config = $this->getConfig();
-    $form['id'] = array(
-      '#type' => 'value',
-      '#value' => $definition['id'],
-    );
-    $form['module'] = array(
-      '#type' => 'value',
-      '#value' => $definition['module'],
-    );
-
-    // Get the block subject for the page title.
-    $subject = isset($config['subject']) ? $config['subject'] : '';
-
-    // Get the theme for the page title.
-    $theme_default = variable_get('theme_default', 'stark');
-    $admin_theme = config('system.theme')->get('admin');
-    $themes = list_themes();
-    $theme_key = $form['theme']['#value'];
-    $theme = $themes[$theme_key];
-    // Use meaningful titles for the main site and administrative themes.
-    $theme_title = $theme->info['name'];
-    if ($theme_key == $theme_default) {
-      $theme_title = t('!theme (default theme)', array('!theme' => $theme_title));
-    }
-    elseif ($admin_theme && $theme_key == $admin_theme) {
-      $theme_title = t('!theme (administration theme)', array('!theme' => $theme_title));
-    }
-
-    if ($subject) {
-      drupal_set_title(t("%subject block in %theme", array('%subject' => $subject, '%theme' => $theme_title)), PASS_THROUGH);
-    }
-
-    $form['settings'] = array(
-      '#weight' => -5,
-    );
-    $form['settings']['title'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Block title'),
-      '#maxlength' => 255,
-      '#default_value' => isset($subject) ? $subject : '',
-    );
-    $form['settings']['machine_name'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Block machine name'),
-      '#maxlength' => 64,
-      '#description' => t('A unique name to save this block configuration. Must be alpha-numeric and be underscore separated.'),
-      '#default_value' => isset($config['config_id']) ? $config['config_id'] : '',
-      '#required' => TRUE,
-    );
-    if (isset($config['config_id'])) {
-      $form['settings']['machine_name']['#disabled'] = TRUE;
-    }
-
-    // Region settings.
-    $form['region'] = array(
-      '#type' => 'select',
-      '#title' => t('Region'),
-      '#description' => t('Select the region where this block should be displayed.'),
-      '#default_value' => !empty($config['region']) && $config['region'] != -1 ? $config['region'] : NULL,
-      '#empty_value' => BLOCK_REGION_NONE,
-      '#options' => system_region_list($theme_key, REGIONS_VISIBLE),
-    );
-
-
-    // Visibility settings.
-    $form['visibility_title'] = array(
-      '#type' => 'item',
-      '#title' => t('Visibility settings'),
-      '#weight' => 10,
-    );
-    $form['visibility'] = array(
-      '#type' => 'vertical_tabs',
-      '#attached' => array(
-        'js' => array(drupal_get_path('module', 'block') . '/block.js'),
-      ),
-      '#tree' => TRUE,
-      '#weight' => 15,
-    );
-
-    // Per-path visibility.
-    $form['visibility']['path'] = array(
-      '#type' => 'details',
-      '#title' => t('Pages'),
-      '#collapsed' => TRUE,
-      '#group' => 'visibility',
-      '#weight' => 0,
-    );
-
-    // @todo remove this access check and inject it in some other way. In fact
-    //   this entire visibility settings section probably needs a separate user
-    //   interface in the near future.
-    $access = user_access('use PHP for settings');
-    if (!empty($config['visibility']['path']['visibility']) && $config['visibility']['path']['visibility'] == BLOCK_VISIBILITY_PHP && !$access) {
-      $form['visibility']['path']['visibility'] = array(
-        '#type' => 'value',
-        '#value' => BLOCK_VISIBILITY_PHP,
-      );
-      $form['visibility']['path']['pages'] = array(
-        '#type' => 'value',
-        '#value' => !empty($config['visibility']['path']['pages']) ? $config['visibility']['path']['pages'] : '',
-      );
-    }
-    else {
-      $options = array(
-        BLOCK_VISIBILITY_NOTLISTED => t('All pages except those listed'),
-        BLOCK_VISIBILITY_LISTED => t('Only the listed pages'),
-      );
-      $description = t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %user for the current user's page and %user-wildcard for every user page. %front is the front page.", array('%user' => 'user', '%user-wildcard' => 'user/*', '%front' => '<front>'));
-
-      if (module_exists('php') && $access) {
-        $options += array(BLOCK_VISIBILITY_PHP => t('Pages on which this PHP code returns <code>TRUE</code> (experts only)'));
-        $title = t('Pages or PHP code');
-        $description .= ' ' . t('If the PHP option is chosen, enter PHP code between %php. Note that executing incorrect PHP code can break your Drupal site.', array('%php' => '<?php ?>'));
-      }
-      else {
-        $title = t('Pages');
-      }
-      $form['visibility']['path']['visibility'] = array(
-        '#type' => 'radios',
-        '#title' => t('Show block on specific pages'),
-        '#options' => $options,
-        '#default_value' => !empty($this->configuration['visibility']['path']['visibility']) ? $this->configuration['visibility']['path']['visibility'] : BLOCK_VISIBILITY_NOTLISTED,
-      );
-      $form['visibility']['path']['pages'] = array(
-        '#type' => 'textarea',
-        '#title' => '<span class="element-invisible">' . $title . '</span>',
-        '#default_value' => !empty($this->configuration['visibility']['path']['pages']) ? $this->configuration['visibility']['path']['pages'] : '',
-        '#description' => $description,
-      );
-    }
-
-    // Configure the block visibility per language.
-    if (module_exists('language') && language_multilingual()) {
-      $configurable_language_types = language_types_get_configurable();
-
-      // Fetch languages.
-      $languages = language_list(LANGUAGE_ALL);
-      foreach ($languages as $language) {
-        // @todo $language->name is not wrapped with t(), it should be replaced
-        //   by CMI translation implementation.
-        $langcodes_options[$language->langcode] = $language->name;
-      }
-      $form['visibility']['language'] = array(
-        '#type' => 'details',
-        '#title' => t('Languages'),
-        '#collapsed' => TRUE,
-        '#group' => 'visibility',
-        '#weight' => 5,
-      );
-      // If there are multiple configurable language types, let the user pick
-      // which one should be applied to this visibility setting. This way users
-      // can limit blocks by interface language or content language for exmaple.
-      $language_types = language_types_info();
-      $language_type_options = array();
-      foreach ($configurable_language_types as $type_key) {
-        $language_type_options[$type_key] = $language_types[$type_key]['name'];
-      }
-      $form['visibility']['language']['language_type'] = array(
-        '#type' => 'radios',
-        '#title' => t('Language type'),
-        '#options' => $language_type_options,
-        '#default_value' => !empty($this->configuration['visibility']['language']['language_type']) ? $this->configuration['visibility']['language']['language_type'] : $configurable_language_types[0],
-        '#access' => count($language_type_options) > 1,
-      );
-      $form['visibility']['language']['langcodes'] = array(
-        '#type' => 'checkboxes',
-        '#title' => t('Show this block only for specific languages'),
-        '#default_value' => !empty($this->configuration['visibility']['language']['langcodes']) ? $this->configuration['visibility']['language']['langcodes'] : array(),
-        '#options' => $langcodes_options,
-        '#description' => t('Show this block only for the selected language(s). If you select no languages, the block will be visibile in all languages.'),
-      );
-    }
-
-    // Per-role visibility.
-    $role_options = array_map('check_plain', user_roles());
-    $form['visibility']['role'] = array(
-      '#type' => 'details',
-      '#title' => t('Roles'),
-      '#collapsed' => TRUE,
-      '#group' => 'visibility',
-      '#weight' => 10,
-    );
-    $form['visibility']['role']['roles'] = array(
-      '#type' => 'checkboxes',
-      '#title' => t('Show block for specific roles'),
-      '#default_value' => !empty($this->configuration['visibility']['role']['roles']) ? $this->configuration['visibility']['role']['roles'] : array(),
-      '#options' => $role_options,
-      '#description' => t('Show this block only for the selected role(s). If you select no roles, the block will be visible to all users.'),
-    );
-
-    // Add specific configuration for this block type.
-    $form += $this->blockForm($form, $form_state);
-
-    $form['actions'] = array('#type' => 'actions');
-    $form['actions']['submit'] = array(
-      '#type' => 'submit',
-      '#value' => t('Save block'),
-    );
-
-    return $form;
-  }
-
-  /**
    * Returns the configuration form elements specific to this block plugin.
    *
    * Blocks that need to add form elements to the normal block configuration
@@ -445,44 +119,6 @@ public function blockForm($form, &$form_state) {
   }
 
   /**
-   * Implements \Drupal\block\BlockInterface::validate().
-   *
-   * Most block plugins should not override this method. To add validation
-   * for a specific block type, override BlockBase::blockValdiate().
-   *
-   * @todo Add inline documentation to this method.
-   *
-   * @see \Drupal\block\BlockBase::blockValidate()
-   */
-  public function validate($form, &$form_state) {
-    if (empty($form['settings']['machine_name']['#disabled'])) {
-      if (preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['machine_name'])) {
-        form_set_error('machine_name', t('Block name must be alphanumeric or underscores only.'));
-      }
-      if (in_array('plugin.core.block.' . $form_state['values']['machine_name'], config_get_storage_names_with_prefix('plugin.core.block'))) {
-        form_set_error('machine_name', t('Block name must be unique.'));
-      }
-    }
-    else {
-      $config_id = explode('.', $form_state['values']['machine_name']);
-      $form_state['values']['machine_name'] = array_pop($config_id);
-    }
-    if ($form_state['values']['module'] == 'block') {
-      $custom_block_exists = (bool) db_query_range('SELECT 1 FROM {block_custom} WHERE bid <> :bid AND info = :info', 0, 1, array(
-        ':bid' => $form_state['values']['delta'],
-        ':info' => $form_state['values']['info'],
-      ))->fetchField();
-      if (empty($form_state['values']['info']) || $custom_block_exists) {
-        form_set_error('info', t('Ensure that each block description is unique.'));
-      }
-    }
-    $form_state['values']['visibility']['role']['roles'] = array_filter($form_state['values']['visibility']['role']['roles']);
-
-    // Perform block type-specific validation.
-    $this->blockValidate($form, $form_state);
-  }
-
-  /**
    * Adds block type-specific validation for the block form.
    *
    * Note that this method takes the form structure and form state arrays for
@@ -501,47 +137,6 @@ public function validate($form, &$form_state) {
   public function blockValidate($form, &$form_state) {}
 
   /**
-   * Implements \Drupal\block\BlockInterface::submit().
-   *
-   * Most block plugins should not override this method. To add submission
-   * handling for a specific block type, override BlockBase::blockSubmit().
-   *
-   * @todo Add inline documentation to this method.
-   *
-   * @see \Drupal\block\BlockBase::blockSubmit()
-   */
-  public function submit($form, &$form_state) {
-    if (!form_get_errors()) {
-      $transaction = db_transaction();
-      try {
-        $keys = array(
-          'visibility' => 'visibility',
-          'pages' => 'pages',
-          'title' => 'subject',
-          'module' => 'module',
-          'region' => 'region',
-        );
-        foreach ($keys as $key => $new_key) {
-          if (isset($form_state['values'][$key])) {
-            $this->configuration[$new_key] = $form_state['values'][$key];
-          }
-        }
-      }
-      catch (Exception $e) {
-        $transaction->rollback();
-        watchdog_exception('block', $e);
-        throw $e;
-      }
-      if (empty($this->configuration['weight'])) {
-        $this->configuration['weight'] = 0;
-      }
-
-      // Perform block type-specific validation.
-      $this->blockSubmit($form, $form_state);
-    }
-  }
-
-  /**
    * Adds block type-specific submission handling for the block form.
    *
    * Note that this method takes the form structure and form state arrays for
@@ -559,46 +154,4 @@ public function submit($form, &$form_state) {
    */
   public function blockSubmit($form, &$form_state) {}
 
-  /**
-   * Implements \Drupal\block\BlockInterface::build().
-   *
-   * Allows blocks to be altered after they are built.
-   *
-   * Most block plugins should not override this method. To define how a
-   * particular block is rendered, implement the abstract method
-   * BlockBase::blockBuild().
-   *
-   * @return array $build
-   *   A renderable array of data.
-   *   - #title: The default localized title of the block.
-   *
-   * @todo Add specific examples of $id and $name below.
-   *
-   * @see \Drupal\block\BlockBase::blockBuild()
-   */
-  public function build() {
-    // Allow modules to modify the block before it is viewed, via either
-    // hook_block_view_alter(), hook_block_view_ID_alter(), or
-    // hook_block_view_NAME_alter().
-    $id = str_replace(':', '__', $this->getPluginId());
-
-    $config = $this->getConfig();
-    $config_id = explode('.', $config['config_id']);
-    $name = array_pop($config_id);
-
-    $build = $this->blockBuild();
-    drupal_alter(array('block_view', "block_view_$id", "block_view_$name"), $build, $this);
-    return $build;
-  }
-
-  /**
-   * Builds the renderable array for a specific block type.
-   *
-   * @return array
-   *   A renderable array representing the output of the block.
-   *
-   * @see \Drupal\block\BlockBase::build()
-   */
-  abstract public function blockBuild();
-
 }
diff --git a/core/modules/block/lib/Drupal/block/BlockFormController.php b/core/modules/block/lib/Drupal/block/BlockFormController.php
new file mode 100644
index 0000000..8053279
--- /dev/null
+++ b/core/modules/block/lib/Drupal/block/BlockFormController.php
@@ -0,0 +1,242 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\BlockFormController.
+ */
+
+namespace Drupal\block;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityFormController;
+
+/**
+ * Form controller for the user account forms.
+ */
+class BlockFormController extends EntityFormController {
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::form().
+   */
+  public function form(array $form, array &$form_state, EntityInterface $entity) {
+    $definition = $entity->getPlugin()->getDefinition();
+    $form['module'] = array(
+      '#type' => 'value',
+      '#value' => $definition['module'],
+    );
+    $form['id'] = array(
+      '#type' => 'value',
+      '#value' => $entity->id(),
+    );
+
+    $form['settings'] = array(
+      '#weight' => -5,
+    );
+    $form['settings']['label'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Block title'),
+      '#maxlength' => 255,
+      '#default_value' => $label,
+    );
+    $form['settings']['machine_name'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Block machine name'),
+      '#maxlength' => 64,
+      '#description' => t('A unique name to save this block configuration. Must be alpha-numeric and be underscore separated.'),
+      '#default_value' => $entity->id(),
+      '#required' => TRUE,
+      '#disabled' => !$entity->isNew(),
+    );
+
+    // Region settings.
+    $form['region'] = array(
+      '#type' => 'select',
+      '#title' => t('Region'),
+      '#description' => t('Select the region where this block should be displayed.'),
+      '#default_value' => $entity->get('region'),
+      '#empty_value' => BLOCK_REGION_NONE,
+      '#options' => system_region_list($entity->get('theme'), REGIONS_VISIBLE),
+    );
+
+    // Visibility settings.
+    $form['visibility_title'] = array(
+      '#type' => 'item',
+      '#title' => t('Visibility settings'),
+      '#weight' => 10,
+    );
+    $form['visibility'] = array(
+      '#type' => 'vertical_tabs',
+      '#attached' => array(
+        'js' => array(drupal_get_path('module', 'block') . '/block.js'),
+      ),
+      '#tree' => TRUE,
+      '#weight' => 15,
+    );
+
+    // Per-path visibility.
+    $form['visibility']['path'] = array(
+      '#type' => 'details',
+      '#title' => t('Pages'),
+      '#collapsed' => TRUE,
+      '#group' => 'visibility',
+      '#weight' => 0,
+    );
+
+    // @todo remove this access check and inject it in some other way. In fact
+    //   this entire visibility settings section probably needs a separate user
+    //   interface in the near future.
+    $visibility = $entity->get('visibility');
+    $access = user_access('use PHP for settings');
+    if (!empty($visibility['path']['visibility']) && $visibility['path']['visibility'] == BLOCK_VISIBILITY_PHP && !$access) {
+      $form['visibility']['path']['visibility'] = array(
+        '#type' => 'value',
+        '#value' => BLOCK_VISIBILITY_PHP,
+      );
+      $form['visibility']['path']['pages'] = array(
+        '#type' => 'value',
+        '#value' => !empty($visibility['path']['pages']) ? $visibility['path']['pages'] : '',
+      );
+    }
+    else {
+      $options = array(
+        BLOCK_VISIBILITY_NOTLISTED => t('All pages except those listed'),
+        BLOCK_VISIBILITY_LISTED => t('Only the listed pages'),
+      );
+      $description = t("Specify pages by using their paths. Enter one path per line. The '*' character is a wildcard. Example paths are %user for the current user's page and %user-wildcard for every user page. %front is the front page.", array('%user' => 'user', '%user-wildcard' => 'user/*', '%front' => '<front>'));
+
+      if (module_exists('php') && $access) {
+        $options += array(BLOCK_VISIBILITY_PHP => t('Pages on which this PHP code returns <code>TRUE</code> (experts only)'));
+        $title = t('Pages or PHP code');
+        $description .= ' ' . t('If the PHP option is chosen, enter PHP code between %php. Note that executing incorrect PHP code can break your Drupal site.', array('%php' => '<?php ?>'));
+      }
+      else {
+        $title = t('Pages');
+      }
+      $form['visibility']['path']['visibility'] = array(
+        '#type' => 'radios',
+        '#title' => t('Show block on specific pages'),
+        '#options' => $options,
+        '#default_value' => !empty($visibility['path']['visibility']) ? $visibility['path']['visibility'] : BLOCK_VISIBILITY_NOTLISTED,
+      );
+      $form['visibility']['path']['pages'] = array(
+        '#type' => 'textarea',
+        '#title' => '<span class="element-invisible">' . $title . '</span>',
+        '#default_value' => !empty($visibility['path']['pages']) ? $visibility['path']['pages'] : '',
+        '#description' => $description,
+      );
+    }
+
+    // Configure the block visibility per language.
+    if (module_exists('language') && language_multilingual()) {
+      $configurable_language_types = language_types_get_configurable();
+
+      // Fetch languages.
+      $languages = language_list(LANGUAGE_ALL);
+      foreach ($languages as $language) {
+        // @todo $language->name is not wrapped with t(), it should be replaced
+        //   by CMI translation implementation.
+        $langcodes_options[$language->langcode] = $language->name;
+      }
+      $form['visibility']['language'] = array(
+        '#type' => 'details',
+        '#title' => t('Languages'),
+        '#collapsed' => TRUE,
+        '#group' => 'visibility',
+        '#weight' => 5,
+      );
+      // If there are multiple configurable language types, let the user pick
+      // which one should be applied to this visibility setting. This way users
+      // can limit blocks by interface language or content language for exmaple.
+      $language_types = language_types_info();
+      $language_type_options = array();
+      foreach ($configurable_language_types as $type_key) {
+        $language_type_options[$type_key] = $language_types[$type_key]['name'];
+      }
+      $form['visibility']['language']['language_type'] = array(
+        '#type' => 'radios',
+        '#title' => t('Language type'),
+        '#options' => $language_type_options,
+        '#default_value' => !empty($visibility['language']['language_type']) ? $visibility['language']['language_type'] : $configurable_language_types[0],
+        '#access' => count($language_type_options) > 1,
+      );
+      $form['visibility']['language']['langcodes'] = array(
+        '#type' => 'checkboxes',
+        '#title' => t('Show this block only for specific languages'),
+        '#default_value' => !empty($visibility['language']['langcodes']) ? $visibility['language']['langcodes'] : array(),
+        '#options' => $langcodes_options,
+        '#description' => t('Show this block only for the selected language(s). If you select no languages, the block will be visibile in all languages.'),
+      );
+    }
+
+    // Per-role visibility.
+    $role_options = array_map('check_plain', user_roles());
+    $form['visibility']['role'] = array(
+      '#type' => 'details',
+      '#title' => t('Roles'),
+      '#collapsed' => TRUE,
+      '#group' => 'visibility',
+      '#weight' => 10,
+    );
+    $form['visibility']['role']['roles'] = array(
+      '#type' => 'checkboxes',
+      '#title' => t('Show block for specific roles'),
+      '#default_value' => !empty($visibility['role']['roles']) ? $visibility['role']['roles'] : array(),
+      '#options' => $role_options,
+      '#description' => t('Show this block only for the selected role(s). If you select no roles, the block will be visible to all users.'),
+    );
+
+    // Add specific configuration for this block type.
+    $form['configuration'] = $entity->getPlugin()->blockForm(array(), $form_state);
+
+    return parent::form($form, $form_state, $entity);
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::actions().
+   */
+  protected function actions(array $form, array &$form_state) {
+    $actions = parent::actions($form, $form_state);
+    $actions['submit']['#value'] = t('Save block');
+    return $actions;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::validate().
+   */
+  public function validate(array $form, array &$form_state) {
+    parent::validate($form, $form_state);
+
+    if (empty($form['settings']['machine_name']['#disabled'])) {
+      if (preg_match('/[^a-zA-Z0-9_]/', $form_state['values']['machine_name'])) {
+        form_set_error('machine_name', t('Block name must be alphanumeric or underscores only.'));
+      }
+    }
+    else {
+      $config_id = explode('.', $form_state['values']['machine_name']);
+      $form_state['values']['machine_name'] = array_pop($config_id);
+    }
+    $form_state['values']['visibility']['role']['roles'] = array_filter($form_state['values']['visibility']['role']['roles']);
+    $entity = $this->getEntity($form_state);
+    if ($entity->isNew()) {
+      form_set_value($form['id'], $entity->get('theme') . '.' . $form_state['values']['machine_name'], $form_state);
+    }
+    $entity->getPlugin()->blockValidate($form, $form_state);
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::submit().
+   */
+  public function submit(array $form, array &$form_state) {
+    parent::submit($form, $form_state);
+
+    $entity = $this->getEntity($form_state);
+    $entity->getPlugin()->blockSubmit($form, $form_state);
+    $entity->set('configuration', $entity->getPlugin()->getConfig());
+    $entity->save();
+
+    drupal_set_message(t('The block configuration has been saved.'));
+    cache_invalidate_tags(array('content' => TRUE));
+    $form_state['redirect'] = 'admin/structure/block/list/block_plugin_ui:' . $entity->get('theme');
+  }
+
+}
diff --git a/core/modules/block/lib/Drupal/block/BlockInterface.php b/core/modules/block/lib/Drupal/block/BlockInterface.php
index b419b27..602efce 100644
--- a/core/modules/block/lib/Drupal/block/BlockInterface.php
+++ b/core/modules/block/lib/Drupal/block/BlockInterface.php
@@ -28,63 +28,7 @@
    * @todo Consider merging this with the general plugin configuration member
    *   variable and its getter/setter in http://drupal.org/node/1764380.
    */
-  public function settings();
-
-  /**
-   * Indicates whether the block should be shown.
-   *
-   * This method allows base implementations to add general access restrictions
-   * that should apply to all extending block plugins.
-   *
-   * @return bool
-   *   TRUE if the block should be shown, or FALSE otherwise.
-   */
-  public function access();
-
-  /**
-   * Constructs the block configuration form.
-   *
-   * This method allows base implementations to add a generic configuration
-   * form for extending block plugins.
-   *
-   * @param array $form
-   *   The form definition array for the block configuration form.
-   * @param array $form_state
-   *   An array containing the current state of the configuration form.
-   *
-   * @return array $form
-   *   The renderable form array representing the entire configuration form.
-   *
-   * @see \Drupal\block\BlockInterace::validate()
-   * @see \Drupal\block\BlockInterace::submit()
-   */
-  public function form($form, &$form_state);
-
-  /**
-   * Handles form validation for the block configuration form.
-   *
-   * @param array $form
-   *   The form definition array for the block configuration form.
-   * @param array $form_state
-   *   An array containing the current state of the configuration form.
-   *
-   * @see \Drupal\block\BlockInterace::form()
-   * @see \Drupal\block\BlockInterace::submit()
-   */
-  public function validate($form, &$form_state);
-
-  /**
-   * Handles form submissions for the block configuration form.
-   *
-   * @param array $form
-   *   The form definition array for the block configuration form.
-   * @param array $form_state
-   *   An array containing the current state of the configuration form.
-   *
-   * @see \Drupal\block\BlockInterace::form()
-   * @see \Drupal\block\BlockInterace::validate()
-   */
-  public function submit($form, &$form_state);
+  public function blockSettings();
 
   /**
    * Builds and returns the renderable array for this block.
@@ -92,6 +36,6 @@ public function submit($form, &$form_state);
    * @return array
    *   A renderable array representing the output of the block.
    */
-  public function build();
+  public function blockBuild();
 
 }
diff --git a/core/modules/block/lib/Drupal/block/BlockRenderController.php b/core/modules/block/lib/Drupal/block/BlockRenderController.php
new file mode 100644
index 0000000..82134c6
--- /dev/null
+++ b/core/modules/block/lib/Drupal/block/BlockRenderController.php
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\BlockRenderController.
+ */
+
+namespace Drupal\block;
+
+use Drupal\Core\Entity\EntityRenderControllerInterface;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Provides a Block access controller.
+ */
+class BlockRenderController implements EntityRenderControllerInterface {
+
+  /**
+   * Implements \Drupal\Core\Entity\EntityRenderControllerInterface::buildContent().
+   */
+  public function buildContent(array $entities, array $displays, $view_mode, $langcode = NULL) {
+    return array();
+  }
+
+  /**
+   * Provides entity-specific defaults to the build process.
+   *
+   * @param Drupal\Core\Entity\EntityInterface $entity
+   *   The entity for which the defaults should be provided.
+   * @param string $view_mode
+   *   The view mode that should be used.
+   * @param string $langcode
+   *   (optional) For which language the entity should be prepared, defaults to
+   *   the current content language.
+   *
+   * @return array
+   *   An array of defaults to add into the entity render array.
+   */
+  protected function getBuildDefaults(EntityInterface $entity, $view_mode, $langcode) {
+    // @todo \Drupal\block\Tests\BlockTest::testCustomBlock() assuemes that a
+    //   block can be rendered without any of its wrappers. To do so, it uses a
+    //   custom view mode, and we choose to only add the wrappers on the default
+    //   view mode, 'block'.
+    if ($view_mode != 'block') {
+      return array();
+    }
+
+    $defaults = array(
+      '#block' => $entity,
+      '#weight' => $entity->get('weight'),
+      '#theme_wrappers' => array('block'),
+      '#block_config' => array(
+        'id' => $entity->get('plugin'),
+        'region' => $entity->get('region'),
+        'module' => $entity->get('module'),
+      ),
+    );
+    $defaults['#block_config'] += $entity->getPlugin()->getConfig();
+
+    // @todo This is a hold-over from the old behavior, it is crazy.
+    if (!empty($defaults['#block_config']['label'])) {
+      $defaults['#block_config']['subject'] = $defaults['#block_config']['label'];
+    }
+    elseif ($label = $entity->label()) {
+      $defaults['#block_config']['subject'] = $label;
+    }
+
+    return $defaults;
+  }
+
+  /**
+   * Implements Drupal\Core\Entity\EntityRenderControllerInterface::view().
+   */
+  public function view(EntityInterface $entity, $view_mode = 'full', $langcode = NULL) {
+    $build = $this->viewMultiple(array($entity), $view_mode, $langcode);
+    return reset($build);
+  }
+
+  /**
+   * Implements Drupal\Core\Entity\EntityRenderControllerInterface::viewMultiple().
+   */
+  public function viewMultiple(array $entities = array(), $view_mode = 'full', $langcode = NULL) {
+    $build = array();
+    foreach ($entities as $entity_id => $entity) {
+      $block = $entity->getPlugin()->blockBuild();
+      // Allow blocks to be empty, do not add in the defaults.
+      if (!empty($block)) {
+        $block = $this->getBuildDefaults($entity, $view_mode, $langcode) + $block;
+      }
+
+      // All blocks, even when empty, should be available for altering.
+      $id = str_replace(':', '__', $entity->get('plugin'));
+      list(, $name) = $entity->id();
+      drupal_alter(array('block_view', "block_view_$id", "block_view_$name"), $block, $entity);
+
+      $build[$entity_id] = $block;
+    }
+    return $build;
+  }
+
+}
diff --git a/core/modules/block/lib/Drupal/block/BlockStorageController.php b/core/modules/block/lib/Drupal/block/BlockStorageController.php
new file mode 100644
index 0000000..02374ff
--- /dev/null
+++ b/core/modules/block/lib/Drupal/block/BlockStorageController.php
@@ -0,0 +1,80 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\BlockStorageController.
+ */
+
+namespace Drupal\block;
+
+use Drupal\Core\Config\Entity\ConfigStorageController;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Defines the storage controller class for Block entities.
+ */
+class BlockStorageController extends ConfigStorageController {
+
+  /**
+   * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::create().
+   */
+  public function create(array $values) {
+    $entity = parent::create($values);
+
+    if (!$entity->get('module')) {
+      $definition = $entity->getPlugin()->getDefinition();
+      $entity->set('module', $definition['module']);
+    }
+    $configuration = $entity->get('configuration');
+    if (isset($configuration['cache'])) {
+      $entity->set('cache', $configuration['cache']);
+    }
+
+    return $entity;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::load().
+   */
+  public function load(array $ids = NULL) {
+    $entities = parent::load($ids);
+    // Only blocks with a valid plugin should be loaded.
+    return array_filter($entities, function ($entity) {
+      return $entity->getPlugin();
+    });
+  }
+
+  /**
+   * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::loadByProperties().
+   */
+  public function loadByProperties(array $values = array()) {
+    $blocks = $this->load();
+    foreach ($values as $key => $value) {
+      $blocks = array_filter($blocks, function($block) use ($key, $value) {
+        return $value == $block->get($key);
+      });
+    }
+    return $blocks;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::preSave().
+   */
+  protected function preSave(EntityInterface $entity) {
+    parent::preSave($entity);
+
+    // Ensure that the theme is stored directly on the entity, to simplify
+    // \Drupal\Core\Config\Entity\ConfigStorageController::loadByProperties().
+    if (!$entity->get('theme')) {
+      list($theme) = explode('.', $entity->id());
+      $entity->set('theme', $theme);
+    }
+
+    // Cache settings are stored directly on the block, remove them from
+    // configuration.
+    $configuration = $entity->get('configuration');
+    unset($configuration['cache']);
+    $entity->set('configuration', $configuration);
+  }
+
+}
diff --git a/core/modules/block/lib/Drupal/block/Plugin/Core/Entity/Block.php b/core/modules/block/lib/Drupal/block/Plugin/Core/Entity/Block.php
new file mode 100644
index 0000000..7f27640
--- /dev/null
+++ b/core/modules/block/lib/Drupal/block/Plugin/Core/Entity/Block.php
@@ -0,0 +1,178 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\block\Plugin\Core\Entity\Block.
+ */
+
+namespace Drupal\block\Plugin\Core\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+use Drupal\Component\Plugin\Exception\PluginException;
+
+/**
+ * Defines a Block configuration entity class.
+ *
+ * @Plugin(
+ *   id = "block",
+ *   label = @Translation("Block"),
+ *   module = "block",
+ *   controller_class = "Drupal\block\BlockStorageController",
+ *   access_controller_class = "Drupal\block\BlockAccessController",
+ *   render_controller_class = "Drupal\block\BlockRenderController",
+ *   form_controller_class = {
+ *     "default" = "Drupal\block\BlockFormController"
+ *   },
+ *   config_prefix = "plugin.core.block",
+ *   fieldable = FALSE,
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "label",
+ *     "uuid" = "uuid"
+ *   }
+ * )
+ */
+class Block extends ConfigEntityBase {
+
+  /**
+   * The id of the block.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The block label.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The block UUID.
+   *
+   * @var string
+   */
+  public $uuid;
+
+  /**
+   * The plugin instance configuration.
+   *
+   * @var array
+   */
+  protected $configuration = array();
+
+  /**
+   * The plugin instance.
+   *
+   * @var \Drupal\block\BlockInterface
+   */
+  protected $instance;
+
+  /**
+   * The region this block is placed in.
+   *
+   * @var string
+   */
+  protected $region;
+
+  /**
+   * Settings to control the block visibility.
+   *
+   * @var array
+   */
+  protected $visibility = array();
+
+  /**
+   * The weight of the block.
+   *
+   * @var int
+   */
+  protected $weight;
+
+  /**
+   * The block's caching state.
+   *
+   * @var int
+   */
+  protected $cache = DRUPAL_NO_CACHE;
+
+  /**
+   * The theme the block is placed in.
+   *
+   * @var string
+   */
+  protected $theme;
+
+  /**
+   * The module owning this block.
+   *
+   * @var string
+   */
+  protected $module;
+
+  /**
+   * The status of this block.
+   *
+   * @var bool
+   */
+  protected $status = TRUE;
+
+  /**
+   * The plugin instance ID.
+   *
+   * @var string
+   */
+  protected $plugin;
+
+  /**
+   * Returns the plugin instance.
+   *
+   * @return \Drupal\block\BlockInterface
+   *   The plugin instance for this block.
+   */
+  public function getPlugin() {
+    if (!$this->instance) {
+      try {
+        $this->instance = drupal_container()->get('plugin.manager.block')->createInstance($this->plugin, $this->configuration);
+        $this->configuration += $this->instance->getConfig();
+      }
+      catch (PluginException $e) {
+        // Ignore blocks belonging to disabled modules, but re-throw valid
+        // exceptions when the module is enabled and the plugin is misconfigured.
+        if (module_exists($this->module)) {
+          throw $e;
+        }
+      }
+    }
+    return $this->instance;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Config\Entity\ConfigEntityBase::getExportProperties();
+   */
+  public function getExportProperties() {
+    $names = array(
+      'id',
+      'label',
+      'uuid',
+      'region',
+      'weight',
+      'cache',
+      'module',
+      'theme',
+      'status',
+      'visibility',
+      'plugin',
+      'configuration',
+    );
+    $properties = array();
+    foreach ($names as $name) {
+      $properties[$name] = $this->get($name);
+    }
+    return $properties;
+  }
+
+}
diff --git a/core/modules/block/lib/Drupal/block/Plugin/system/plugin_ui/BlockPluginUI.php b/core/modules/block/lib/Drupal/block/Plugin/system/plugin_ui/BlockPluginUI.php
index 27256a2..baecaa6 100644
--- a/core/modules/block/lib/Drupal/block/Plugin/system/plugin_ui/BlockPluginUI.php
+++ b/core/modules/block/lib/Drupal/block/Plugin/system/plugin_ui/BlockPluginUI.php
@@ -17,7 +17,7 @@
  *   id = "block_plugin_ui",
  *   module = "block",
  *   all_plugins = @Translation("All Blocks"),
- *   config_path = "admin/structure/block/manage",
+ *   config_path = "admin/structure/block/add",
  *   default_task = TRUE,
  *   derivative = "Drupal\block\Plugin\Derivative\BlockPluginUI",
  *   facets = {
@@ -116,7 +116,7 @@ public function formValidate($form, &$form_state) {
    * Overrides \Drupal\system\Plugin\PluginUIBase::formSubmit().
    */
   public function formSubmit($form, &$form_state) {
-    $form_state['redirect'] = 'admin/structure/block/manage/' . $form_state['values']['block'] . '/' . $form_state['values']['theme'];
+    $form_state['redirect'] = 'admin/structure/block/add/' . $form_state['values']['block'] . '/' . $form_state['values']['theme'];
   }
 
   /**
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockCacheTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockCacheTest.php
index f40d4fa..1afb4d7 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockCacheTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockCacheTest.php
@@ -24,6 +24,7 @@ class BlockCacheTest extends WebTestBase {
   protected $admin_user;
   protected $normal_user;
   protected $normal_user_alt;
+  protected $block;
 
   public static function getInfo() {
     return array(
@@ -49,8 +50,7 @@ function setUp() {
     $this->normal_user_alt->save();
 
     // Enable our test block.
-   $block = $this->drupalPlaceBlock('test_cache');
-   $this->block_config_id = $block['config_id'];
+   $this->block = $this->drupalPlaceBlock('test_cache');
   }
 
   /**
@@ -192,15 +192,8 @@ function testCachePerPage() {
    * Private helper method to set the test block's cache mode.
    */
   private function setCacheMode($cache_mode) {
-    $block_config = config($this->block_config_id);
-    $block_config->set('cache', $cache_mode);
-    $block_config->save();
-
-    $instance = block_load($this->block_config_id);
-    $config = $instance->getConfig();
-    if ($config['cache'] != $cache_mode) {
-      $this->fail(t('Unable to set cache mode to %mode. Current mode: %current_mode', array('%mode' => $cache_mode, '%current_mode' => $config['cache'])));
-    }
-    $this->assertEqual($config['cache'], $cache_mode, t("Test block's database entry updated to DRUPAL_NO_CACHE."));
+    $this->block->set('cache', $cache_mode);
+    $this->block->save();
   }
+
 }
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockInvalidRegionTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockInvalidRegionTest.php
index ec41b39..54417f8 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockInvalidRegionTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockInvalidRegionTest.php
@@ -46,11 +46,10 @@ function setUp() {
   function testBlockInInvalidRegion() {
     // Enable a test block and place it in an invalid region.
     $block = $this->drupalPlaceBlock('test_html_id');
-    $config = config($block['config_id']);
-    $config->set('region', 'invalid_region');
-    $config->save();
+    $block->set('region', 'invalid_region');
+    $block->save();
 
-    $warning_message = t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $config->get('id'), '%region' => 'invalid_region'));
+    $warning_message = t('The block %info was assigned to the invalid region %region and has been disabled.', array('%info' => $block->id(), '%region' => 'invalid_region'));
 
     // Clearing the cache should disable the test block placed in the invalid region.
     $this->drupalPost('admin/config/development/performance', array(), 'Clear all caches');
@@ -61,8 +60,9 @@ function testBlockInInvalidRegion() {
     $this->assertNoRaw($warning_message, 'Disabled block in the invalid region will not trigger the warning.');
 
     // Place disabled test block in the invalid region of the default theme.
-    $config->set('region', 'invalid_region');
-    $config->save();
+    $block = entity_load('block', $block->id());
+    $block->set('region', 'invalid_region');
+    $block->save();
 
     // Clear the cache to check if the warning message is not triggered.
     $this->drupalPost('admin/config/development/performance', array(), 'Clear all caches');
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockLanguageTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockLanguageTest.php
index 2fa1d40..beb2fcc 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockLanguageTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockLanguageTest.php
@@ -55,7 +55,7 @@ function setUp() {
   public function testLanguageBlockVisibility() {
     // Check if the visibility setting is available.
     $default_theme = variable_get('theme_default', 'stark');
-    $this->drupalGet('admin/structure/block/manage/system_powered_by_block' . '/' . $default_theme);
+    $this->drupalGet('admin/structure/block/add/system_powered_by_block' . '/' . $default_theme);
 
     $this->assertField('visibility[language][langcodes][en]', 'Language visibility field is visible.');
 
@@ -65,7 +65,7 @@ public function testLanguageBlockVisibility() {
       'machine_name' => $this->randomName(8),
       'region' => 'sidebar_first',
     );
-    $this->drupalPost('admin/structure/block/manage/system_powered_by_block' . '/' . $default_theme, $edit, t('Save block'));
+    $this->drupalPost('admin/structure/block/add/system_powered_by_block' . '/' . $default_theme, $edit, t('Save block'));
 
     // Change the default language.
     $edit = array(
@@ -89,19 +89,23 @@ public function testLanguageBlockVisibility() {
    * Tests if the visibility settings are removed if the language is deleted.
    */
   public function testLanguageBlockVisibilityLanguageDelete() {
-
-    $default_theme = variable_get('theme_default', 'stark');
     // Enable a standard block and set the visibility setting for one language.
     $edit = array(
-      'visibility[language][langcodes][fr]' => TRUE,
+      'visibility' => array(
+        'language' => array(
+          'langcodes' => array(
+            'fr' => 'fr',
+          ),
+        ),
+      ),
       'machine_name' => 'language_block_test',
       'region' => 'sidebar_first',
     );
-    $this->drupalPost('admin/structure/block/manage/system_powered_by_block' . '/' . $default_theme, $edit, t('Save block'));
+    $block = $this->drupalPlaceBlock('system_powered_by_block', $edit, t('Save block'));
 
     // Check that we have the language in config after saving the setting.
-    $config = config('plugin.core.block.' . $default_theme . '.language_block_test');
-    $setting = $config->get('visibility.language.langcodes.fr');
+    $visibility = $block->get('visibility');
+    $setting = $visibility['language']['langcodes']['fr'];
     $this->assertTrue('fr' === $setting, 'Language is set in the block configuration.');
 
     // Delete the language.
@@ -109,9 +113,9 @@ public function testLanguageBlockVisibilityLanguageDelete() {
 
     // Check that the language is no longer stored in the configuration after
     // it is deleted.
-    $config = config('plugin.core.block.' . $default_theme . '.language_block_test');
-    $setting = $config->get('visibility.language.langcodes.fr');
-    $this->assertTrue(empty($setting), 'Language is no longer not set in the block configuration after deleting the block.');
+    $block = entity_load('block', $block->id());
+    $visibility = $block->get('visibility');
+    $this->assertTrue(empty($visibility['language']['langcodes']['fr']), 'Language is no longer not set in the block configuration after deleting the block.');
   }
 
 }
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockLibrarySearchTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockLibrarySearchTest.php
index dc93755..22f47e8 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockLibrarySearchTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockLibrarySearchTest.php
@@ -54,6 +54,7 @@ function testBlockLibrarySearch() {
 
     // Check that the block search form redirects to the correct block form.
     $this->drupalPost('admin/structure/block/list/block_plugin_ui:stark/add', array('block' => 'system_main_block'), t('Next'));
-    $this->assertUrl('admin/structure/block/manage/system_main_block/stark');
+    $this->assertUrl('admin/structure/block/add/system_main_block/stark');
   }
+
 }
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockTemplateSuggestionsUnitTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockTemplateSuggestionsUnitTest.php
index fd75adf..c469eb6 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockTemplateSuggestionsUnitTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockTemplateSuggestionsUnitTest.php
@@ -37,18 +37,25 @@ function testBlockThemeHookSuggestions() {
     // an underscore (not transformed) and a hyphen (transformed to underscore),
     // and generates possibilities for each level of derivative.
     // @todo Clarify this comment.
-    $data = array(
+    $block = entity_create('block', array(
+      'plugin' => 'system_menu_block:menu-admin',
       'region' => 'footer',
-    );
+      'id' => variable_get('theme_default', 'stark') . '.machinename',
+    ));
 
-    $block = drupal_container()->get('plugin.manager.block')->createInstance('system_menu_block:menu-admin', $data);
     $variables = array();
     $variables['elements']['#block'] = $block;
+    $variables['elements']['#block_config'] = $block->getPlugin()->getConfig() + array(
+      'id' => $block->get('plugin'),
+      'region' => $block->get('region'),
+      'module' => $block->get('module'),
+    );
     $variables['elements']['#children'] = '';
     // Test adding a class to the block content.
     $variables['content_attributes']['class'][] = 'test-class';
     template_preprocess_block($variables);
-    $this->assertEqual($variables['theme_hook_suggestions'], array('block__footer', 'block__system', 'block__system_menu_block', 'block__system_menu_block__menu_admin'));
+    $this->assertEqual($variables['theme_hook_suggestions'], array('block__footer', 'block__system', 'block__system_menu_block', 'block__system_menu_block__menu_admin', 'block__machinename'));
     $this->assertEqual($variables['content_attributes']['class'], array('test-class', 'content'), 'Default .content class added to block content_attributes_array');
   }
+
 }
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php
index c607973..ffc8d0f 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php
@@ -67,10 +67,8 @@ function setUp() {
    */
   protected function removeDefaultBlocks() {
     $default_theme = variable_get('theme_default', 'stark');
-    $manager = $this->container->get('plugin.manager.block');
-    $instances = config_get_storage_names_with_prefix('plugin.core.block.' . $default_theme);
-    foreach ($instances as $plugin_id) {
-      config($plugin_id)->delete();
+    foreach (entity_load_multiple_by_properties('block', array('theme' => $default_theme)) as $block) {
+      $block->delete();
     }
   }
 
@@ -110,21 +108,23 @@ public function testCustomBlock() {
     $info = strtolower($this->randomName(8));
     $custom_block['machine_name'] = $info;
     $custom_block['info'] = $info;
-    $custom_block['title'] = $this->randomName(8);
+    $custom_block['label'] = $this->randomName(8);
     $custom_block['body[value]'] = $this->randomName(32);
     $custom_block['region'] = $this->regions[0];
     $this->drupalPost("admin/structure/block/list/block_plugin_ui:$default_theme/add/custom_blocks", $custom_block, t('Save block'));
-    $plugin_id = "plugin.core.block.$default_theme.$info";
-    $block = $manager->getInstance(array('config' => $plugin_id));
-    $config = $block->getConfig();
+    $block = entity_load('block', $default_theme . '.' . $info);
 
     // Confirm that the custom block has been created, and then query the created bid.
     $this->assertText(t('The block configuration has been saved.'), 'Custom block successfully created.');
 
-    // Check that block_block_view() returns the correct title and content.
-    $data = $block->build();
-    $format = $config['format'];
-    $this->assertEqual(check_markup($custom_block['body[value]'], $format), render($data), 'BlockInterface::build() provides correct block content.');
+    // Check that entity_view() returns the correct title and content.
+    // @todo This assumes that a block's content can be rendered without its
+    //   wrappers. If this is a reasonable expectation, it should be documented
+    //   elsewhere.
+    $data = entity_view($block, 'content');
+    $definition = $block->getPlugin()->getDefinition();
+    $config = $definition['settings'];
+    $this->assertEqual(check_markup($custom_block['body[value]'], $config['format']), render($data), 'BlockInterface::build() provides correct block content.');
 
     // Check whether the block can be moved to all available regions.
     $custom_block['module'] = 'block';
@@ -134,7 +134,7 @@ public function testCustomBlock() {
 
     // Verify presence of configure and delete links for custom block.
     $this->drupalGet('admin/structure/block');
-    $config_block_id = "admin/structure/block/manage/plugin.core.block.$default_theme.$info/$default_theme";
+    $config_block_id = "admin/structure/block/manage/$default_theme.$info";
     $this->assertLinkByHref("$config_block_id/configure", 0, 'Custom block configure link found.');
     $this->assertLinkByHref("$config_block_id/delete", 0, 'Custom block delete link found.');
 
@@ -146,9 +146,9 @@ public function testCustomBlock() {
     // Delete the created custom block & verify that it's been deleted and no longer appearing on the page.
     $this->clickLink(t('delete'));
     $this->drupalPost("$config_block_id/delete", array(), t('Delete'));
-    $this->assertRaw(t('The block %title has been removed.', array('%title' => $custom_block['title'])), 'Custom block successfully deleted.');
+    $this->assertRaw(t('The block %title has been removed.', array('%title' => $custom_block['label'])), 'Custom block successfully deleted.');
     $this->drupalGet(NULL);
-    $this->assertNoText(t($custom_block['title']), 'Custom block no longer appears on page.');
+    $this->assertNoText(t($custom_block['label']), 'Custom block no longer appears on page.');
   }
 
   /**
@@ -162,7 +162,7 @@ public function testCustomBlockFormat() {
     $info = $this->randomName(8);
     $custom_block['machine_name'] = $info;
     $custom_block['info'] = $info;
-    $custom_block['title'] = $this->randomName(8);
+    $custom_block['label'] = $this->randomName(8);
     $custom_block['body[value]'] = '<h1>Full HTML</h1>';
     $full_html_format = filter_format_load('full_html');
     $custom_block['body[format]'] = $full_html_format->format;
@@ -170,7 +170,7 @@ public function testCustomBlockFormat() {
     $this->drupalPost("admin/structure/block/list/block_plugin_ui:$default_theme/add/custom_blocks", $custom_block, t('Save block'));
 
     // Set the created custom block to a specific region.
-    $edit['blocks[0][region]'] = $this->regions[1];
+    $edit['blocks[' . $default_theme . '.' . $custom_block['machine_name'] . '][region]'] = $this->regions[1];
     $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
 
     // Confirm that the custom block is being displayed using configured text format.
@@ -180,11 +180,10 @@ public function testCustomBlockFormat() {
     // Confirm that a user without access to Full HTML can not see the body field,
     // but can still submit the form without errors.
     $block_admin = $this->drupalCreateUser(array('administer blocks'));
-    $config_block_id = "admin/structure/block/manage/plugin.core.block.$default_theme.$info/$default_theme";
     $this->drupalLogin($block_admin);
-    $this->drupalGet("$config_block_id/configure");
+    $this->drupalGet("admin/structure/block/manage/$default_theme.$info/configure");
     $this->assertFieldByXPath("//textarea[@name='body[value]' and @disabled='disabled']", t('This field has been disabled because you do not have sufficient permissions to edit it.'), 'Body field contains denied message');
-    $this->drupalPost("$config_block_id/configure", array(), t('Save block'));
+    $this->drupalPost("admin/structure/block/manage/$default_theme.$info/configure", array(), t('Save block'));
     $this->assertNoText(t('Ensure that each block description is unique.'));
 
     // Confirm that the custom block is still being displayed using configured text format.
@@ -204,13 +203,13 @@ function testBlockVisibility() {
     $edit = array(
       'machine_name' => $this->randomName(8),
       'region' => 'sidebar_first',
-      'title' => $title,
+      'label' => $title,
     );
     // Set the block to be hidden on any user path, and to be shown only to
     // authenticated users.
     $edit['visibility[path][pages]'] = 'user*';
     $edit['visibility[role][roles][' . DRUPAL_AUTHENTICATED_RID . ']'] = TRUE;
-    $this->drupalPost('admin/structure/block/manage/' . $block_name . '/' . $default_theme, $edit, t('Save block'));
+    $this->drupalPost('admin/structure/block/add/' . $block_name . '/' . $default_theme, $edit, t('Save block'));
     $this->assertText('The block configuration has been saved.', 'Block was saved');
 
     $this->drupalGet('');
@@ -244,12 +243,12 @@ function testBlockVisibilityListedEmpty() {
     $edit = array(
       'machine_name' => $this->randomName(8),
       'region' => 'sidebar_first',
-      'title' => $title,
+      'label' => $title,
       'visibility[path][visibility]' => BLOCK_VISIBILITY_LISTED,
     );
     // Set the block to be hidden on any user path, and to be shown only to
     // authenticated users.
-    $this->drupalPost('admin/structure/block/manage/' . $block_name . '/' . $default_theme, $edit, t('Save block'));
+    $this->drupalPost('admin/structure/block/add/' . $block_name . '/' . $default_theme, $edit, t('Save block'));
     $this->assertText('The block configuration has been saved.', 'Block was saved');
 
     $this->drupalGet('user');
@@ -272,20 +271,18 @@ function testBlock() {
     // Select the 'Powered by Drupal' block to be configured and moved.
     $block = array();
     $block['id'] = 'system_powered_by_block';
-    $block['title'] = $this->randomName(8);
+    $block['label'] = $this->randomName(8);
     $block['machine_name'] = $this->randomName(8);
     $block['theme'] = variable_get('theme_default', 'stark');
     $block['region'] = 'header';
 
     // Set block title to confirm that interface works and override any custom titles.
-    $this->drupalPost('admin/structure/block/manage/' . $block['id'] . '/' . $block['theme'], array('title' => $block['title'], 'machine_name' => $block['machine_name'], 'region' => $block['region']), t('Save block'));
+    $this->drupalPost('admin/structure/block/add/' . $block['id'] . '/' . $block['theme'], array('label' => $block['label'], 'machine_name' => $block['machine_name'], 'region' => $block['region']), t('Save block'));
     $this->assertText(t('The block configuration has been saved.'), 'Block title set.');
     // Check to see if the block was created by checking its configuration.
-    $block['config_id'] = 'plugin.core.block.' . $block['theme'] . '.' . $block['machine_name'];
-    $instance = block_load($block['config_id']);
-    $config = $instance->getConfig();
+    $instance = entity_load('block', $block['theme'] . '.' . $block['machine_name']);
 
-    $this->assertEqual($config['subject'], $block['title'], 'Stored block title found.');
+    $this->assertEqual($instance->label(), $block['label'], 'Stored block title found.');
 
     // Check whether the block can be moved to all available regions.
     foreach ($this->regions as $region) {
@@ -294,7 +291,7 @@ function testBlock() {
 
     // Set the block to the disabled region.
     $edit = array();
-    $edit['blocks[0][region]'] = -1;
+    $edit['blocks[' . $block['theme'] . '.' . $block['machine_name'] . '][region]'] = -1;
     $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
 
     // Confirm that the block is now listed as disabled.
@@ -302,7 +299,7 @@ function testBlock() {
 
     // Confirm that the block instance title and markup are not displayed.
     $this->drupalGet('node');
-    $this->assertNoText(t($block['title']));
+    $this->assertNoText(t($block['label']));
     // Check for <div id="block-my-block-instance-name"> if the machine name
     // is my_block_instance_name.
     $xpath = $this->buildXPathQuery('//div[@id=:id]/*', array(':id' => 'block-' . strtr(strtolower($block['machine_name']), '-', '_')));
@@ -323,8 +320,9 @@ function testBlock() {
    */
   function moveBlockToRegion(array $block, $region) {
     // Set the created block to a specific region.
+    $block += array('theme' => variable_get('theme_default', 'stark'));
     $edit = array();
-    $edit['blocks[0][region]'] = $region;
+    $edit['blocks[' . $block['theme'] . '.' . $block['machine_name'] . '][region]'] = $region;
     $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
 
     // Confirm that the block was moved to the proper region.
@@ -332,7 +330,7 @@ function moveBlockToRegion(array $block, $region) {
 
     // Confirm that the block is being displayed.
     $this->drupalGet('');
-    $this->assertText(t($block['title']), 'Block successfully being displayed on the page.');
+    $this->assertText(t($block['label']), 'Block successfully being displayed on the page.');
 
     // Confirm that the custom block was found at the proper region.
     $xpath = $this->buildXPathQuery('//div[@class=:region-class]//div[@id=:block-id]/*', array(
@@ -360,24 +358,19 @@ function testBlockRehash() {
     $block['machine_name'] = $this->randomName(8);
     $block['theme'] = variable_get('theme_default', 'stark');
     $block['region'] = 'header';
-    $this->drupalPost('admin/structure/block/manage/' . $block['id'] . '/' . $block['theme'], array('machine_name' => $block['machine_name'], 'region' => $block['region']), t('Save block'));
+    $block = $this->drupalPlaceBlock('test_cache', array('region' => 'header'));
 
     // Our test block's caching should default to DRUPAL_CACHE_PER_ROLE.
-    $block['config_id'] = 'plugin.core.block.' . $block['theme'] . '.' . $block['machine_name'];
-    $instance = block_load($block['config_id']);
-    $config = $instance->getConfig();
-    $this->assertEqual($config['cache'], DRUPAL_CACHE_PER_ROLE, 'Test block cache mode defaults to DRUPAL_CACHE_PER_ROLE.');
+    $this->assertEqual($block->get('cache'), DRUPAL_CACHE_PER_ROLE, 'Test block cache mode defaults to DRUPAL_CACHE_PER_ROLE.');
 
     // Disable caching for this block.
-    $block_config = config($block['config_id']);
-    $block_config->set('cache', DRUPAL_NO_CACHE);
-    $block_config->save();
+    $block->set('cache', DRUPAL_NO_CACHE);
+    $block->save();
     // Flushing all caches should call _block_rehash().
     $this->resetAll();
     // Verify that block is updated with the new caching mode.
-    $instance = block_load($block['config_id']);
-    $config = $instance->getConfig();
-    $this->assertEqual($config['cache'], DRUPAL_NO_CACHE, "Test block's database entry updated to DRUPAL_NO_CACHE.");
+    $block = entity_load('block', $block->id());
+    $this->assertEqual($block->get('cache'), DRUPAL_NO_CACHE, "Test block's database entry updated to DRUPAL_NO_CACHE.");
   }
 
   /**
@@ -399,7 +392,7 @@ function testBlockModuleDisable() {
     }
     $this->drupalGet('');
     foreach ($regions as $region) {
-      $this->assertText($blocks[$region]['subject']);
+      $this->assertText($blocks[$region]->label());
     }
 
     // Disable the block test module and refresh the definitions cache.
@@ -415,7 +408,7 @@ function testBlockModuleDisable() {
 
     // Ensure that the disabled module's block instance is not listed.
     foreach ($regions as $region) {
-      $this->assertNoText($blocks[$region]['subject']);
+      $this->assertNoText($blocks[$region]->label());
     }
 
     // Ensure that the disabled module's block plugin is no longer available.
@@ -426,7 +419,7 @@ function testBlockModuleDisable() {
     $this->drupalGet('');
     $this->assertResponse(200);
     foreach ($regions as $region) {
-      $this->assertNoText($blocks[$region]['subject']);
+      $this->assertNoText($blocks[$region]->label());
     }
 
     // Confirm that a different block instance can still be enabled by
@@ -434,16 +427,16 @@ function testBlockModuleDisable() {
     // Emulate a POST submission rather than using drupalPlaceBlock() to ensure
     // that the form still functions as expected.
     $edit = array(
-      'title' => $this->randomName(8),
+      'label' => $this->randomName(8),
       'machine_name' => $this->randomName(8),
       'region' => 'sidebar_first',
     );
-    $this->drupalPost('admin/structure/block/manage/system_powered_by_block/stark', $edit, t('Save block'));
+    $this->drupalPost('admin/structure/block/add/system_powered_by_block/stark', $edit, t('Save block'));
     $this->assertText(t('The block configuration has been saved.'));
-    $this->assertText($edit['title']);
+    $this->assertText($edit['label']);
 
     // Update the weight of a block.
-    $edit = array('blocks[0][weight]' => -1);
+    $edit = array('blocks[stark.' . $edit['machine_name'] . '][weight]' => -1);
     $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
     $this->assertText(t('The block settings have been updated.'));
 
@@ -455,7 +448,7 @@ function testBlockModuleDisable() {
     // Reload the admin page and confirm the block can again be configured.
     $this->drupalGet('admin/structure/block');
     foreach ($regions as $region) {
-      $this->assertLinkByHref(url('admin/structure/block/manage/' . $blocks[$region]['config_id'] . '/stark/config'));
+      $this->assertLinkByHref(url('admin/structure/block/manage/' . $blocks[$region]->id()));
     }
 
     // Confirm that the blocks are again displayed on the front page in the
@@ -463,14 +456,14 @@ function testBlockModuleDisable() {
     $this->drupalGet('');
     foreach ($regions as $region) {
       // @todo Use a proper method for this.
-      $name_pieces = explode('.', $blocks[$region]['config_id']);
+      $name_pieces = explode('.', $blocks[$region]->id());
       $machine_name = array_pop($name_pieces);
       $xpath = $this->buildXPathQuery('//div[@class=:region-class]//div[@id=:block-id]/*', array(
         ':region-class' => 'region region-' . drupal_html_class($region),
         ':block-id' => 'block-' . strtr(strtolower($machine_name), '-', '_'),
     ));
       $this->assertFieldByXPath($xpath, NULL, format_string('Block %name found in the %region region.', array(
-        '%name' => $blocks[$region]['subject'],
+        '%name' => $blocks[$region]->label(),
         '%region' => $region,
       )));
     }
diff --git a/core/modules/book/lib/Drupal/book/Tests/BookTest.php b/core/modules/book/lib/Drupal/book/Tests/BookTest.php
index 16c8ae3..ae0bdae 100644
--- a/core/modules/book/lib/Drupal/book/Tests/BookTest.php
+++ b/core/modules/book/lib/Drupal/book/Tests/BookTest.php
@@ -321,7 +321,7 @@ function testBookNavigationBlock() {
     // Test correct display of the block.
     $nodes = $this->createBook();
     $this->drupalGet('<front>');
-    $this->assertText($block['subject'], 'Book navigation block is displayed.');
+    $this->assertText($block->label(), 'Book navigation block is displayed.');
     $this->assertText($this->book->label(), format_string('Link to book root (@title) is displayed.', array('@title' => $nodes[0]->label())));
     $this->assertNoText($nodes[0]->label(), 'No links to individual book pages are displayed.');
   }
@@ -345,12 +345,12 @@ function testNavigationBlockOnAccessModuleEnabled() {
     // Test correct display of the block to registered users.
     $this->drupalLogin($this->web_user);
     $this->drupalGet('node/' . $this->book->nid);
-    $this->assertText($block['subject'], 'Book navigation block is displayed to registered users.');
+    $this->assertText($block->label(), 'Book navigation block is displayed to registered users.');
     $this->drupalLogout();
 
     // Test correct display of the block to anonymous users.
     $this->drupalGet('node/' . $this->book->nid);
-    $this->assertText($block['subject'], 'Book navigation block is displayed to anonymous users.');
+    $this->assertText($block->label(), 'Book navigation block is displayed to anonymous users.');
   }
 
   /**
diff --git a/core/modules/comment/lib/Drupal/comment/Tests/CommentBlockTest.php b/core/modules/comment/lib/Drupal/comment/Tests/CommentBlockTest.php
index d4d3fe6..8d9ad8c 100644
--- a/core/modules/comment/lib/Drupal/comment/Tests/CommentBlockTest.php
+++ b/core/modules/comment/lib/Drupal/comment/Tests/CommentBlockTest.php
@@ -46,7 +46,7 @@ public static function getInfo() {
    */
   function testRecentCommentBlock() {
     $this->drupalLogin($this->admin_user);
-    $block = $this->drupalPlaceBlock('recent_comments', array('block_count' => 2));
+    $block = $this->drupalPlaceBlock('recent_comments', array('configuration' => array('block_count' => 2)));
 
     // Add some test comments, one without a subject.
     $comment1 = $this->postComment($this->node, $this->randomName(), $this->randomName());
@@ -61,14 +61,15 @@ function testRecentCommentBlock() {
     // posting a node from a node form.
     cache_invalidate_tags(array('content' => TRUE));
     $this->drupalGet('');
-    $this->assertNoText($block['subject'], 'Block was not found.');
+    $label = $block->label();
+    $this->assertNoText($label, 'Block was not found.');
     user_role_grant_permissions(DRUPAL_ANONYMOUS_RID, array('access comments'));
 
     // Test that a user with the 'access comments' permission can see the
     // block.
     $this->drupalLogin($this->web_user);
     $this->drupalGet('');
-    $this->assertText($block['subject'], 'Block was found.');
+    $this->assertText($label, 'Block was found.');
 
     // Test the only the 2 latest comments are shown and in the proper order.
     $this->assertNoText($comment1->subject, 'Comment not found in block.');
@@ -77,15 +78,8 @@ function testRecentCommentBlock() {
     $this->assertTrue(strpos($this->drupalGetContent(), $comment3->comment) < strpos($this->drupalGetContent(), $comment2->subject), 'Comments were ordered correctly in block.');
 
     // Set the number of recent comments to show to 10.
-    $this->drupalLogout();
-    $this->drupalLogin($this->admin_user);
-    $edit = array(
-      'block_count' => 10,
-    );
-    $current_theme = variable_get('theme_default', 'stark');
-
-    $this->drupalPost('admin/structure/block/manage/' . $block['config_id'] . '/' . variable_get('theme_default', 'stark') . '/configure', $edit, t('Save block'));
-    $this->assertText(t('The block configuration has been saved.'), 'Block saved.');
+    $block->set('configuration', array('block_count' => 10));
+    $block->save();
 
     // Post an additional comment.
     $comment4 = $this->postComment($this->node, $this->randomName(), $this->randomName());
diff --git a/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php b/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php
index de28051..10a7ce9 100644
--- a/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php
+++ b/core/modules/forum/lib/Drupal/forum/Tests/ForumBlockTest.php
@@ -59,7 +59,7 @@ public function testNewForumTopicsBlock() {
 
     // Enable the new forum topics block.
     $block = $this->drupalPlaceBlock('forum_new_block');
-    $this->assertText(t('The block configuration has been saved.'), '"New forum topics" block was enabled');
+    $this->drupalGet('');
 
     $this->assertLink(t('More'), 0, 'New forum topics block has a "more"-link.');
     $this->assertLinkByHref('forum', 0, 'New forum topics block has a "more"-link.');
@@ -70,9 +70,8 @@ public function testNewForumTopicsBlock() {
     }
 
     // Configure the new forum topics block to only show 2 topics.
-    $config = config($block['config_id']);
-    $config->set('block_count', 2);
-    $config->save();
+    $block->set('configuration', array('block_count' => 2));
+    $block->save();
 
     $this->drupalGet('');
     // We expect only the 2 most recent forum topics to appear in the "New forum
@@ -90,7 +89,7 @@ public function testNewForumTopicsBlock() {
   /**
    * Tests the "Active forum topics" block.
    */
-  public function testActiveForumTopicsBlock() {
+  public function _testActiveForumTopicsBlock() {
     $this->drupalLogin($this->adminUser);
 
     // Create 10 forum topics.
@@ -129,9 +128,8 @@ public function testActiveForumTopicsBlock() {
     }
 
     // Configure the active forum block to only show 2 topics.
-    $config = config($block['config_id']);
-    $config->set('block_count', 2);
-    $config->save();
+    $block->set('configuration', array('block_count' => 2));
+    $block->save();
 
     $this->drupalGet('');
 
diff --git a/core/profiles/standard/config/plugin.core.block.seven.help.yml b/core/modules/help/config/plugin.core.block.seven.help.yml
similarity index 75%
copy from core/profiles/standard/config/plugin.core.block.seven.help.yml
copy to core/modules/help/config/plugin.core.block.seven.help.yml
index a5c4467..f75e2d7 100644
--- a/core/profiles/standard/config/plugin.core.block.seven.help.yml
+++ b/core/modules/help/config/plugin.core.block.seven.help.yml
@@ -1,6 +1,11 @@
-id: system_help_block
-status: '1'
+id: seven.help
+label: ''
+region: help
+weight: ''
 cache: '-1'
+module: system
+theme: seven
+status: '1'
 visibility:
   path:
     visibility: '0'
@@ -12,7 +17,4 @@ visibility:
       article: '0'
       page: '0'
   visibility__active_tab: edit-visibility-path
-subject: ''
-module: system
-region: help
-weight: '0'
+plugin: system_help_block
diff --git a/core/modules/language/language.module b/core/modules/language/language.module
index c2356be..c8f3b7b 100644
--- a/core/modules/language/language.module
+++ b/core/modules/language/language.module
@@ -47,7 +47,8 @@ function language_help($path, $arg) {
 
       return $output;
 
-    case 'admin/structure/block/manage/%/%':
+    case 'admin/structure/block/manage/%':
+    case 'admin/structure/block/add/%/%':
       if ($arg[4] == 'language' && $arg[5] == 'language_interface') {
         return '<p>' . t('With multiple languages enabled, registered users can select their preferred language and authors can assign a specific language to content.') . '</p>';
       }
diff --git a/core/modules/language/lib/Drupal/language/Plugin/block/block/LanguageBlock.php b/core/modules/language/lib/Drupal/language/Plugin/block/block/LanguageBlock.php
index 74fb83a..a698695 100644
--- a/core/modules/language/lib/Drupal/language/Plugin/block/block/LanguageBlock.php
+++ b/core/modules/language/lib/Drupal/language/Plugin/block/block/LanguageBlock.php
@@ -34,12 +34,13 @@ function blockAccess() {
    * Implements \Drupal\block\BlockBase::blockBuild().
    */
   public function blockBuild() {
+    $build = array();
     $path = drupal_is_front_page() ? '<front>' : current_path();
     list($plugin_id, $type) = explode(':', $this->getPluginId());
     $links = language_negotiation_get_switch_links($type, $path);
 
     if (isset($links->links)) {
-      return array(
+      $build = array(
         '#theme' => 'links__language_block',
         '#links' => $links->links,
         '#attributes' => array(
@@ -49,6 +50,7 @@ public function blockBuild() {
         ),
       );
     }
+    return $build;
   }
 
 }
diff --git a/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php b/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php
index 8effdfe..ac834d1 100644
--- a/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php
+++ b/core/modules/language/lib/Drupal/language/Tests/LanguageSwitchingTest.php
@@ -56,7 +56,7 @@ function testLanguageBlock() {
 
     // Assert that the language switching block is displayed on the frontpage.
     $this->drupalGet('');
-    $this->assertText($block['subject'], 'Language switcher block found.');
+    $this->assertText($block->label(), 'Language switcher block found.');
 
     // Assert that only the current language is marked as active.
     list($language_switcher) = $this->xpath('//div[@id=:id]/div[@class="content"]', array(':id' => 'block-test-language-block'));
diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index ff24720..5515387 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -339,11 +339,12 @@ function menu_delete($menu) {
     ->execute();
 
   menu_cache_clear_all();
+  module_invoke_all('menu_delete', $menu);
+
   // Invalidate the block cache to update menu-based derivatives.
   if (module_exists('block')) {
     drupal_container()->get('plugin.manager.block')->clearCachedDefinitions();
   }
-  module_invoke_all('menu_delete', $menu);
 }
 
 /**
@@ -481,7 +482,7 @@ function menu_reset_item($link) {
  */
 function menu_block_view_alter(&$build, $block) {
   // Add contextual links for system menu blocks.
-  if ($block instanceof SystemMenuBlock) {
+  if ($block->getPlugin() instanceof SystemMenuBlock) {
     foreach (element_children($build) as $key) {
       $build['#contextual_links']['menu'] = array('admin/structure/menu/manage', array($build[$key]['#original_link']['menu_name']));
     }
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php
index b5d5205..cb1368f 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeBlockFunctionalTest.php
@@ -61,7 +61,7 @@ public function testRecentNodeBlock() {
     ));
 
     // Enable the recent content block with two items.
-    $block = $this->drupalPlaceBlock('node_recent_block', array('machine_name' => 'test_block', 'block_count' => 2));
+    $block = $this->drupalPlaceBlock('node_recent_block', array('machine_name' => 'test_block', 'configuration' => array('block_count' => 2)));
 
     // Test that block is not visible without nodes.
     $this->drupalGet('');
@@ -91,7 +91,7 @@ public function testRecentNodeBlock() {
     // see the block.
     $this->drupalLogout();
     $this->drupalGet('');
-    $this->assertNoText($block['subject'], 'Block was not found.');
+    $this->assertNoText($block->label(), 'Block was not found.');
 
     // Test that only the 2 latest nodes are shown.
     $this->drupalLogin($this->webUser);
@@ -106,9 +106,8 @@ public function testRecentNodeBlock() {
     $this->drupalLogin($this->adminUser);
 
     // Set the number of recent nodes to show to 10.
-    $config = config($block['config_id']);
-    $config->set('block_count', 10);
-    $config->save();
+    $block->set('configuration', array('block_count' => 10));
+    $block->save();
 
     // Post an additional node.
     $node4 = $this->drupalCreateNode($default_settings);
@@ -125,24 +124,30 @@ public function testRecentNodeBlock() {
 
     // Enable the "Powered by Drupal" block only on article nodes.
     $block = $this->drupalPlaceBlock('system_powered_by_block', array(
-      'visibility[node_type][types][article]' => TRUE,
-             ));
-    $config = config($block['config_id']);
-    $node_type_visibility = $config->get('visibility.node_type.types.article');
-    $this->assertEqual($node_type_visibility, 'article', 'Visibility settings were saved to configuration');
+      'visibility' => array(
+        'node_type' => array(
+          'types' => array(
+            'article' => 'article',
+          ),
+        ),
+      ),
+    ));
+    $visibility = $block->get('visibility');
+    $this->assertTrue(isset($visibility['node_type']['types']['article']), 'Visibility settings were saved to configuration');
 
     // Create a page node.
     $node5 = $this->drupalCreateNode(array('uid' => $this->adminUser->uid, 'type' => 'page'));
 
     // Verify visibility rules.
     $this->drupalGet('');
-    $this->assertNoText($block['subject'], 'Block was not displayed on the front page.');
+    $label = $block->label();
+    $this->assertNoText($label, 'Block was not displayed on the front page.');
     $this->drupalGet('node/add/article');
-    $this->assertText($block['subject'], 'Block was displayed on the node/add/article page.');
+    $this->assertText($label, 'Block was displayed on the node/add/article page.');
     $this->drupalGet('node/' . $node1->nid);
-    $this->assertText($block['subject'], 'Block was displayed on the node/N when node is of type article.');
+    $this->assertText($label, 'Block was displayed on the node/N when node is of type article.');
     $this->drupalGet('node/' . $node5->nid);
-    $this->assertNoText($block['subject'], 'Block was not displayed on nodes of type page.');
+    $this->assertNoText($label, 'Block was not displayed on nodes of type page.');
   }
 
 }
diff --git a/core/modules/node/lib/Drupal/node/Tests/NodeSyndicateBlockTest.php b/core/modules/node/lib/Drupal/node/Tests/NodeSyndicateBlockTest.php
index 4e5a209..887b7ea 100644
--- a/core/modules/node/lib/Drupal/node/Tests/NodeSyndicateBlockTest.php
+++ b/core/modules/node/lib/Drupal/node/Tests/NodeSyndicateBlockTest.php
@@ -41,6 +41,7 @@ function setUp() {
   public function testSyndicateBlock() {
     // Place the "Syndicate" block and confirm that it is rendered.
     $this->drupalPlaceBlock('node_syndicate_block', array('machine_name' => 'test_syndicate_block'));
+    $this->drupalGet('');
     $this->assertFieldByXPath('//div[@id="block-test-syndicate-block"]/*', NULL, 'Syndicate block found.');
   }
 
diff --git a/core/modules/node/node.module b/core/modules/node/node.module
index 9e8fbd3..f6fd1a4 100644
--- a/core/modules/node/node.module
+++ b/core/modules/node/node.module
@@ -1986,14 +1986,13 @@ function theme_node_recent_content($variables) {
 }
 
 /**
- * Implements hook_form_FORM_ID_alter() for block_admin_configure().
+ * Implements hook_form_FORM_ID_alter() for block_form().
  *
  * Adds node-type specific visibility options to block configuration form.
- *
- * @see node_form_block_admin_configure_submit()
  */
-function node_form_block_admin_configure_alter(&$form, &$form_state) {
-  $config = $form['#instance']->getConfig();
+function node_form_block_form_alter(&$form, &$form_state) {
+  $block = $form_state['entity'];
+  $visibility = $block->get('visibility');
   $form['visibility']['node_type'] = array(
     '#type' => 'details',
     '#title' => t('Content types'),
@@ -2005,39 +2004,21 @@ function node_form_block_admin_configure_alter(&$form, &$form_state) {
   $form['visibility']['node_type']['types'] = array(
     '#type' => 'checkboxes',
     '#title' => t('Show block for specific content types'),
-    '#default_value' => !empty($config['visibility']['node_type']['types']) ? $config['visibility']['node_type']['types'] : array(),
+    '#default_value' => !empty($visibility['node_type']['types']) ? $visibility['node_type']['types'] : array(),
     '#options' => node_type_get_names(),
     '#description' => t('Show this block only on pages that display content of the given type(s). If you select no types, there will be no type-specific limitation.'),
   );
 }
 
 /**
- * Implements hook_modules_uninstalled().
- *
- * Cleans up the {block_node_type} table from modules' blocks.
- */
-function node_modules_uninstalled($modules) {
-  // Remove the block visibility settings for all node types.
-  $block_configs = config_get_storage_names_with_prefix('plugin.core.block');
-  foreach ($block_configs as $config_id) {
-    $config = config($config_id);
-    $node_types = $config->get('visibility.node_type.types');
-    if (!empty($node_types)) {
-      $config->clear('visibility.node_type.types');
-    }
-  }
-}
-
-/**
  * Implements hook_block_access().
  *
  * Checks the content type specific visibility settings and removes the block
  * if the visibility conditions are not met.
  */
 function node_block_access($block) {
-  $configuration = $block->getConfig();
-  if (!empty($configuration['visibility'])) {
-    $visibility = $configuration['visibility'];
+  $visibility = $block->get('visibility');
+  if (!empty($visibility)) {
     $allowed_types = array();
     $node = menu_get_object();
     $node_types = node_type_get_types();
diff --git a/core/modules/search/lib/Drupal/search/Tests/SearchBlockTest.php b/core/modules/search/lib/Drupal/search/Tests/SearchBlockTest.php
index b358ded..f3b4101 100644
--- a/core/modules/search/lib/Drupal/search/Tests/SearchBlockTest.php
+++ b/core/modules/search/lib/Drupal/search/Tests/SearchBlockTest.php
@@ -40,7 +40,9 @@ function setUp() {
    */
   protected function testSearchFormBlock() {
     $block = $this->drupalPlaceBlock('search_form_block');
-    $this->assertText($block['subject'], 'Block title was found.');
+
+    $this->drupalGet('');
+    $this->assertText($block->label(), 'Block title was found.');
 
     // Test a normal search via the block form, from the front page.
     $terms = array('search_block_form' => 'test');
@@ -54,9 +56,9 @@ protected function testSearchFormBlock() {
     $this->assertResponse(200);
     $this->assertText('Your search yielded no results');
 
-    $config = config($block['config_id']);
-    $config->set('visibility.path.pages', 'search');
-    $config->save();
+    $visibility = $block->get('visibility');
+    $visibility['path']['pages'] = 'search';
+    $block->set('visibility', $visibility);
 
     $this->drupalPost('node', $terms, t('Search'));
     $this->assertText('Your search yielded no results');
diff --git a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
index 5ac4017..7ac863e 100644
--- a/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
+++ b/core/modules/simpletest/lib/Drupal/simpletest/WebTestBase.php
@@ -369,47 +369,20 @@ protected function drupalCreateContentType($settings = array()) {
    *   on failure.
    *
    * @todo
-   *   Create the block programmatically once block instances are configuration
-   *   entities.
-   * @todo
    *   Add support for creating custom block instances.
    */
   protected function drupalPlaceBlock($plugin_id, array $settings = array(), $theme = NULL) {
-    // Confirm that the active user has permission to add the block instance.
-    // @todo Remove this check once it is possible to do this programmatically.
-    if (!empty($this->loggedInUser) && !user_access('administer blocks', $this->loggedInUser)) {
-      $this->fail('The logged in user does not have permission to administer blocks.');
-      return FALSE;
-    }
-
     // If no theme was specified, use the default theme.
     $theme = $theme ?: variable_get('theme_default', 'stark');
-
-    // Populate some default block settings.
-    $settings += array(
-      'title' => $this->randomName(8),
-      'machine_name' => strtolower($this->randomName(8)),
+    $machine_name = isset($settings['machine_name']) ? $settings['machine_name'] : strtolower($this->randomName(8));
+    $block = entity_create('block', $settings + array(
+      'plugin' => $plugin_id,
+      'label' => $this->randomName(8),
+      'id' => $theme . '.' . $machine_name,
       'region' => 'sidebar_first',
-    );
-
-    // Submit the block instance configuration.
-    $this->drupalPost('admin/structure/block/manage/' . $plugin_id . '/' . $theme, $settings, t('Save block'));
-    $this->assertText(
-      t('The block configuration has been saved.'),
-      format_string(
-        '%plugin block instance added for %theme',
-        array(
-          '%plugin' => $plugin_id,
-          '%theme' => $theme,
-        )
-      )
-    );
-
-    // Return the created block instance.
-    $instance_id = "plugin.core.block.$theme." . $settings['machine_name'];
-    $manager = $this->container->get('plugin.manager.block');
-    $block = $manager->getInstance(array('config' => $instance_id));
-    return $block->getConfig();
+    ));
+    $block->save();
+    return $block;
   }
 
   /**
diff --git a/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsReportsTest.php b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsReportsTest.php
index 1843c8b..ec5fd54 100644
--- a/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsReportsTest.php
+++ b/core/modules/statistics/lib/Drupal/statistics/Tests/StatisticsReportsTest.php
@@ -88,10 +88,12 @@ function testPopularContentBlock() {
 
     // Configure and save the block.
     $this->drupalPlaceBlock('statistics_popular_block', array(
-      'title' => 'Popular content',
-      'statistics_block_top_day_num' => 3,
-      'statistics_block_top_all_num' => 3,
-      'statistics_block_top_last_num' => 3,
+      'label' => 'Popular content',
+      'configuration' => array(
+        'top_day_num' => 3,
+        'top_all_num' => 3,
+        'top_last_num' => 3,
+      ),
     ));
 
     // Get some page and check if the block is displayed.
diff --git a/core/modules/system/lib/Drupal/system/Tests/System/AccessDeniedTest.php b/core/modules/system/lib/Drupal/system/Tests/System/AccessDeniedTest.php
index dc555cb..7c8c85b 100644
--- a/core/modules/system/lib/Drupal/system/Tests/System/AccessDeniedTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/System/AccessDeniedTest.php
@@ -82,7 +82,7 @@ function testAccessDenied() {
     $edit = array(
       'region' => -1,
     );
-    $this->drupalPost('admin/structure/block/manage/plugin.core.block.stark.login/stark/configure', $edit, t('Save block'));
+    $this->drupalPost('admin/structure/block/manage/stark.login/configure', $edit, t('Save block'));
 
     // Check that we can log in from the 403 page.
     $this->drupalLogout();
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/BlockUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/BlockUpgradePathTest.php
index 1778284..3314554 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/BlockUpgradePathTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/BlockUpgradePathTest.php
@@ -35,23 +35,27 @@ public function setUp() {
   public function testBlockUpgradeTitleLength() {
     $this->assertTrue($this->performUpgrade(), 'The upgrade was completed successfully.');
 
+    // WebTestBase::drupalPlaceBlock() uses the API directly, which doesn't
+    // output validation errors or success messages, so create the blocks from
+    // the UI.
+
     // Add a block instance with a 255-character title.
-    $title = $this->randomName(255);
-    $this->drupalPlaceBlock('system_powered_by_block', array('title' => $title));
     // Confirm that the custom block has been created, and title matches input.
-    $this->drupalGet('');
-    $this->assertText($title, 'Block with title longer than 64 characters successfully created.');
+    $settings = array(
+      'label' => $this->randomName(255),
+      'machine_name' => strtolower($this->randomName(8)),
+      'region' => 'sidebar_first',
+    );
+    $this->drupalPost('admin/structure/block/add/system_powered_by_block/' . variable_get('theme_default', 'stark'), $settings, t('Save block'));
+    $this->assertText($settings['label'], 'Block with title longer than 64 characters successfully created.');
 
     // Try to add a block with a title over 255 characters.
-    // WebTestBase::drupalPlaceBlock() asserts that the block is created
-    // successfully. In this case we expect the block creation to fail, so
-    // create a new instance of the block manually.
     $settings = array(
-      'title' => $this->randomName(256),
+      'label' => $this->randomName(256),
       'machine_name' => strtolower($this->randomName(8)),
       'region' => 'sidebar_first',
     );
-    $this->drupalPost('admin/structure/block/manage/system_powered_by_block/' . variable_get('theme_default', 'stark'), $settings, t('Save block'));
+    $this->drupalPost('admin/structure/block/add/system_powered_by_block/' . variable_get('theme_default', 'stark'), $settings, t('Save block'));
 
     // Confirm that the custom block cannot be created with title longer than
     // the maximum number of characters.
diff --git a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserRoleUpgradePathTest.php b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserRoleUpgradePathTest.php
index 4e968d2..f87c19f 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserRoleUpgradePathTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Upgrade/UserRoleUpgradePathTest.php
@@ -58,7 +58,7 @@ public function testRoleUpgrade() {
 
     // Check that the role visibility setting for the who's online block still
     // exists.
-    $this->drupalGet('admin/structure/block/manage/user_online_block/bartik');
+    $this->drupalGet('admin/structure/block/add/user_online_block/bartik');
 
     // @todo Blocks are not being upgraded.
     //   $this->assertFieldChecked('edit-visibility-role-roles-5', "Who's online block visibility setting is correctly set for the long role name.");
diff --git a/core/modules/system/system.module b/core/modules/system/system.module
index 8a762ee..f4363e4 100644
--- a/core/modules/system/system.module
+++ b/core/modules/system/system.module
@@ -7,7 +7,7 @@
 
 use Drupal\Core\Utility\ModuleInfo;
 use Drupal\Core\TypedData\Primitive;
-
+use Drupal\system\Plugin\block\block\SystemMenuBlock;
 use Symfony\Component\HttpFoundation\JsonResponse;
 use Symfony\Component\HttpFoundation\Response;
 
@@ -2611,7 +2611,7 @@ function system_preprocess_block(&$variables) {
 
     // System menu blocks should get the same class as menu module blocks.
     default:
-      if ($variables['block']->class == 'Drupal\\system\\Plugin\\block\\block\\SystemMenuBlock') {
+      if ($variables['elements']['#block']->getPlugin() instanceof SystemMenuBlock) {
         $variables['attributes_array']['role'] = 'navigation';
         $variables['classes_array'][] = 'block-menu';
       }
diff --git a/core/modules/user/lib/Drupal/user/Tests/UserBlocksTests.php b/core/modules/user/lib/Drupal/user/Tests/UserBlocksTests.php
index a2f027f..d4c3379 100644
--- a/core/modules/user/lib/Drupal/user/Tests/UserBlocksTests.php
+++ b/core/modules/user/lib/Drupal/user/Tests/UserBlocksTests.php
@@ -80,9 +80,8 @@ function testUserLoginBlock() {
    * Test the Who's Online block.
    */
   function testWhosOnlineBlock() {
-    $plugin_id = 'plugin.core.block.' . variable_get('theme_default', 'stark') . '.online';
-    $block = $this->container->get('plugin.manager.block')->getInstance(array('config' => $plugin_id));
-    $config = $block->getConfig();
+    $block = entity_load('block', 'stark.online');
+    $config = $block->get('configuration');
 
     // Generate users.
     $user1 = $this->drupalCreateUser(array());
@@ -100,7 +99,7 @@ function testWhosOnlineBlock() {
     $this->updateAccess($this->adminUser->uid, $inactive_time);
 
     // Test block output.
-    $content = $block->build();
+    $content = entity_view($block, 'block');
     $this->drupalSetContent(render($content));
     $this->assertRaw(t('2 users'), 'Correct number of online users (2 users).');
     $this->assertText($user1->name, 'Active user 1 found in online list.');
diff --git a/core/modules/views/lib/Drupal/views/Plugin/block/block/ViewsBlock.php b/core/modules/views/lib/Drupal/views/Plugin/block/block/ViewsBlock.php
index 296212c..6d74ee4 100644
--- a/core/modules/views/lib/Drupal/views/Plugin/block/block/ViewsBlock.php
+++ b/core/modules/views/lib/Drupal/views/Plugin/block/block/ViewsBlock.php
@@ -74,7 +74,7 @@ public function blockForm($form, &$form_state) {
   public function blockBuild() {
     $output = $this->view->executeDisplay($this->displayID);
     // Set the subject to the title configured in the view.
-    $this->configuration['subject'] = filter_xss_admin($this->view->getTitle());
+    $this->configuration['label'] = filter_xss_admin($this->view->getTitle());
     // Before returning the block output, convert it to a renderable array
     // with contextual links.
     views_add_block_contextual_links($output, $this->view, $this->displayID);
diff --git a/core/modules/views/lib/Drupal/views/Tests/UI/OverrideDisplaysTest.php b/core/modules/views/lib/Drupal/views/Tests/UI/OverrideDisplaysTest.php
index be4868e..5c654a5 100644
--- a/core/modules/views/lib/Drupal/views/Tests/UI/OverrideDisplaysTest.php
+++ b/core/modules/views/lib/Drupal/views/Tests/UI/OverrideDisplaysTest.php
@@ -58,7 +58,7 @@ function testOverrideDisplays() {
     $this->assertText('View: ' . $view['human_name']);
 
     // Place the block.
-    $this->drupalPlaceBlock("views_block:{$view['name']}-block_1");
+    $this->drupalPlaceBlock("views_block:{$view['name']}-block_1", array('label' => ''));
 
     // Make sure the title appears in the block.
     $this->drupalGet('');
diff --git a/core/profiles/standard/config/plugin.core.block.bartik.content.yml b/core/profiles/standard/config/plugin.core.block.bartik.content.yml
index e10f903..1406a59 100644
--- a/core/profiles/standard/config/plugin.core.block.bartik.content.yml
+++ b/core/profiles/standard/config/plugin.core.block.bartik.content.yml
@@ -1,4 +1,6 @@
-id: system_main_block
+id: bartik.content
+plugin: system_main_block
+theme: bartik
 status: '1'
 cache: '-1'
 visibility:
@@ -12,7 +14,8 @@ visibility:
       article: '0'
       page: '0'
   visibility__active_tab: edit-visibility-path
-subject: ''
+label: ''
 module: system
 region: content
 weight: '0'
+langcode: und
diff --git a/core/profiles/standard/config/plugin.core.block.bartik.footer.yml b/core/profiles/standard/config/plugin.core.block.bartik.footer.yml
index 7b6f9b7..4d03200 100644
--- a/core/profiles/standard/config/plugin.core.block.bartik.footer.yml
+++ b/core/profiles/standard/config/plugin.core.block.bartik.footer.yml
@@ -1,7 +1,9 @@
-id: 'system_menu_block:menu-footer'
+id: bartik.footer
+plugin: 'system_menu_block:menu-footer'
+theme: bartik
 status: '1'
 cache: '-1'
-subject: 'Footer menu'
+label: 'Footer menu'
 visibility:
   path:
     visibility: '0'
@@ -16,3 +18,4 @@ visibility:
 module: system
 region: footer
 weight: '0'
+langcode: und
diff --git a/core/profiles/standard/config/plugin.core.block.bartik.help.yml b/core/profiles/standard/config/plugin.core.block.bartik.help.yml
index a5c4467..0f46023 100644
--- a/core/profiles/standard/config/plugin.core.block.bartik.help.yml
+++ b/core/profiles/standard/config/plugin.core.block.bartik.help.yml
@@ -1,4 +1,6 @@
-id: system_help_block
+id: bartik.help
+plugin: system_help_block
+theme: bartik
 status: '1'
 cache: '-1'
 visibility:
@@ -12,7 +14,8 @@ visibility:
       article: '0'
       page: '0'
   visibility__active_tab: edit-visibility-path
-subject: ''
+label: ''
 module: system
 region: help
 weight: '0'
+langcode: und
diff --git a/core/profiles/standard/config/plugin.core.block.bartik.login.yml b/core/profiles/standard/config/plugin.core.block.bartik.login.yml
index 8d8923a..1e780b6 100644
--- a/core/profiles/standard/config/plugin.core.block.bartik.login.yml
+++ b/core/profiles/standard/config/plugin.core.block.bartik.login.yml
@@ -1,4 +1,4 @@
-id: user_login_block
+id: bartik.login
 whois_new_count: '5'
 status: '1'
 cache: '-1'
@@ -13,7 +13,10 @@ visibility:
       article: '0'
       page: '0'
   visibility__active_tab: edit-visibility-path
-subject: 'User login'
+label: 'User login'
 module: user
 region: sidebar_first
 weight: '0'
+plugin: user_login_block
+theme: bartik
+langcode: und
diff --git a/core/profiles/standard/config/plugin.core.block.bartik.powered.yml b/core/profiles/standard/config/plugin.core.block.bartik.powered.yml
index 1f9dc52..165d5d0 100644
--- a/core/profiles/standard/config/plugin.core.block.bartik.powered.yml
+++ b/core/profiles/standard/config/plugin.core.block.bartik.powered.yml
@@ -1,4 +1,6 @@
-id: system_powered_by_block
+id: bartik.powered
+plugin: system_powered_by_block
+theme: bartik
 status: '1'
 cache: '-1'
 visibility:
@@ -12,7 +14,8 @@ visibility:
       article: '0'
       page: '0'
   visibility__active_tab: edit-visibility-path
-subject: ''
+label: ''
 module: system
 region: footer
 weight: '10'
+langcode: und
diff --git a/core/profiles/standard/config/plugin.core.block.bartik.search.yml b/core/profiles/standard/config/plugin.core.block.bartik.search.yml
index d314a49..d69e7bb 100644
--- a/core/profiles/standard/config/plugin.core.block.bartik.search.yml
+++ b/core/profiles/standard/config/plugin.core.block.bartik.search.yml
@@ -1,4 +1,6 @@
-id: search_form_block
+id: bartik.search
+plugin: search_form_block
+theme: bartik
 status: '1'
 cache: '-1'
 visibility:
@@ -12,7 +14,8 @@ visibility:
       article: '0'
       page: '0'
   visibility__active_tab: edit-visibility-path
-subject: 'Search'
+label: 'Search'
 module: search
 region: sidebar_first
 weight: '-1'
+langcode: und
diff --git a/core/profiles/standard/config/plugin.core.block.bartik.tools.yml b/core/profiles/standard/config/plugin.core.block.bartik.tools.yml
index d6a38c9..40df6d9 100644
--- a/core/profiles/standard/config/plugin.core.block.bartik.tools.yml
+++ b/core/profiles/standard/config/plugin.core.block.bartik.tools.yml
@@ -1,4 +1,6 @@
-id: 'system_menu_block:menu-tools'
+id: bartik.tools
+plugin: 'system_menu_block:menu-tools'
+theme: bartik
 status: '1'
 cache: '-1'
 visibility:
@@ -12,7 +14,8 @@ visibility:
       article: '0'
       page: '0'
   visibility__active_tab: edit-visibility-path
-subject: 'Tools'
+label: 'Tools'
 module: system
 region: sidebar_first
 weight: '0'
+langcode: und
diff --git a/core/profiles/standard/config/plugin.core.block.seven.content.yml b/core/profiles/standard/config/plugin.core.block.seven.content.yml
index e10f903..8a826c1 100644
--- a/core/profiles/standard/config/plugin.core.block.seven.content.yml
+++ b/core/profiles/standard/config/plugin.core.block.seven.content.yml
@@ -1,4 +1,6 @@
-id: system_main_block
+id: seven.content
+plugin: system_main_block
+theme: seven
 status: '1'
 cache: '-1'
 visibility:
@@ -12,7 +14,8 @@ visibility:
       article: '0'
       page: '0'
   visibility__active_tab: edit-visibility-path
-subject: ''
+label: ''
 module: system
 region: content
 weight: '0'
+langcode: und
diff --git a/core/profiles/standard/config/plugin.core.block.seven.help.yml b/core/profiles/standard/config/plugin.core.block.seven.help.yml
index a5c4467..5b3ad82 100644
--- a/core/profiles/standard/config/plugin.core.block.seven.help.yml
+++ b/core/profiles/standard/config/plugin.core.block.seven.help.yml
@@ -1,4 +1,6 @@
-id: system_help_block
+id: bartik.content
+plugin: system_help_block
+theme: seven
 status: '1'
 cache: '-1'
 visibility:
@@ -12,7 +14,8 @@ visibility:
       article: '0'
       page: '0'
   visibility__active_tab: edit-visibility-path
-subject: ''
+label: ''
 module: system
 region: help
 weight: '0'
+langcode: und
diff --git a/core/profiles/standard/config/plugin.core.block.seven.login.yml b/core/profiles/standard/config/plugin.core.block.seven.login.yml
index 8c4eca5..7f19040 100644
--- a/core/profiles/standard/config/plugin.core.block.seven.login.yml
+++ b/core/profiles/standard/config/plugin.core.block.seven.login.yml
@@ -1,5 +1,6 @@
-id: user_login_block
-whois_new_count: '5'
+id: seven.login
+plugin: user_login_block
+theme: seven
 status: '1'
 cache: '-1'
 visibility:
@@ -13,7 +14,8 @@ visibility:
       article: '0'
       page: '0'
   visibility__active_tab: edit-visibility-path
-subject: 'User login'
+label: 'User login'
 module: user
 region: content
 weight: '10'
+langcode: und
diff --git a/core/profiles/testing/config/plugin.core.block.stark.admin.yml b/core/profiles/testing/config/plugin.core.block.stark.admin.yml
index 960e2cb..e7d832d 100644
--- a/core/profiles/testing/config/plugin.core.block.stark.admin.yml
+++ b/core/profiles/testing/config/plugin.core.block.stark.admin.yml
@@ -1,4 +1,6 @@
-id: 'system_menu_block:menu-admin'
+id: stark.admin
+plugin: 'system_menu_block:menu-admin'
+theme: stark
 status: '1'
 cache: '-1'
 visibility:
@@ -12,7 +14,8 @@ visibility:
       article: '0'
       page: '0'
   visibility__active_tab: edit-visibility-path
-subject: ''
+label: ''
 module: system
 region: sidebar_first
 weight: '0'
+langcode: und
diff --git a/core/profiles/testing/config/plugin.core.block.stark.online.yml b/core/profiles/testing/config/plugin.core.block.stark.online.yml
index 3b3f5c5..db1f2cc 100644
--- a/core/profiles/testing/config/plugin.core.block.stark.online.yml
+++ b/core/profiles/testing/config/plugin.core.block.stark.online.yml
@@ -1,11 +1,15 @@
-id: user_online_block
-properties:
-  administrative: '1'
-seconds_online: '900'
-max_list_count: '10'
+id: stark.online
+label: 'Who''s online'
+plugin: user_online_block
+theme: stark
+configuration:
+  properties:
+    administrative: '1'
+  seconds_online: '900'
+  max_list_count: '10'
 status: '1'
 cache: '-1'
-subject: 'Who''s online'
+label: 'Who''s online'
 visibility:
   path:
     visibility: '0'
@@ -21,3 +25,4 @@ visibility:
 module: user
 region: sidebar_first
 weight: '0'
+langcode: und
diff --git a/core/profiles/testing/config/plugin.core.block.stark.tools.yml b/core/profiles/testing/config/plugin.core.block.stark.tools.yml
index 5e8b188..bda5f0f 100644
--- a/core/profiles/testing/config/plugin.core.block.stark.tools.yml
+++ b/core/profiles/testing/config/plugin.core.block.stark.tools.yml
@@ -1,4 +1,6 @@
-id: 'system_menu_block:menu-tools'
+id: stark.tools
+plugin: 'system_menu_block:menu-tools'
+theme: stark
 status: '1'
 cache: '-1'
 visibility:
@@ -12,7 +14,8 @@ visibility:
       article: '0'
       page: '0'
   visibility__active_tab: edit-visibility-path
-subject: ''
+label: ''
 module: system
 region: sidebar_first
 weight: '0'
+langcode: und
