diff --git a/core/MAINTAINERS.txt b/core/MAINTAINERS.txt
index 03aa2611367114e46031e6eb4f614dc11ca813a5..c289eb5a6ecdaf5fa4593a2d0f6a6a0167dc410c 100644
--- a/core/MAINTAINERS.txt
+++ b/core/MAINTAINERS.txt
@@ -200,6 +200,9 @@ Contact module
 Contextual module
 - Daniel F. Kudwien 'sun' http://drupal.org/user/54136
 
+Custom block module
+- Lee Rowlands 'larowlan' http://drupal.org/user/395439
+
 Database Logging module
 - Khalid Baheyeldin 'kbahey' http://drupal.org/user/4063
 
diff --git a/core/modules/block/block.install b/core/modules/block/block.install
index 6cdb67cf95dff31625345d5563c93501241e6247..bdbbecc933e939d2585822e2bdecf08984517894 100644
--- a/core/modules/block/block.install
+++ b/core/modules/block/block.install
@@ -4,6 +4,7 @@
  * @file
  * Install, update and uninstall functions for the block module.
  */
+use Drupal\Component\Uuid\Uuid;
 
 /**
  * Implements hook_schema().
@@ -42,7 +43,7 @@ function block_update_dependencies() {
   );
   // Migrate users.data after User module prepared the tables.
   $dependencies['block'][8005] = array(
-    'user' => 8011,
+    'user' => 8016,
   );
   return $dependencies;
 }
@@ -193,6 +194,246 @@ function block_update_8006() {
   update_module_enable(array('custom_block'));
 }
 
+/**
+ * Migrate {block_custom} to {custom_block}.
+ *
+ * Note this table now resides in custom_block_schema() but for 7.x to 8.x
+ * upgrades, changes must be made from block module as custom_block module is
+ * only enabled during upgrade process.
+ */
+function block_update_8007() {
+  // Add the {custom_block} table.
+  db_create_table('custom_block', array(
+    'description' => 'Stores contents of custom-made blocks.',
+    'fields' => array(
+      'id' => array(
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'description' => "The block's {custom_block}.id.",
+      ),
+      'uuid' => array(
+        'description' => 'Unique Key: Universally unique identifier for this entity.',
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => FALSE,
+      ),
+      'info' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Block description.',
+      ),
+      // Defaults to NULL in order to avoid a brief period of potential
+      // deadlocks on the index.
+      'revision_id' => array(
+        'description' => 'The current {block_custom_revision}.revision_id version identifier.',
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => FALSE,
+        'default' => NULL,
+      ),
+      'type' => array(
+        'description' => 'The type of this custom block.',
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+      'langcode' => array(
+        'description' => 'The {language}.langcode of this node.',
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+    ),
+    'primary key' => array('id'),
+    'indexes' => array(
+      'block_custom_type'   => array(array('type', 4)),
+    ),
+    'unique keys' => array(
+      'revision_id' => array('revision_id'),
+      'uuid' => array('uuid'),
+      'info' => array('info'),
+    ),
+    'foreign keys' => array(
+      'custom_block_revision' => array(
+        'table' => 'custom_block_revision',
+        'columns' => array('revision_id' => 'revision_id'),
+      ),
+    ),
+  ));
+  // Add the {custom_block_revision} table.
+  db_create_table('custom_block_revision', array(
+    'description' => 'Stores contents of custom-made blocks.',
+    'fields' => array(
+      'id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => "The block's {custom_block}.id.",
+      ),
+      // Defaults to NULL in order to avoid a brief period of potential
+      // deadlocks on the index.
+      'revision_id' => array(
+        'description' => 'The current version identifier.',
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'log' => array(
+        'description' => 'The log entry explaining the changes in this version.',
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+      ),
+      'info' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Block description.',
+      ),
+    ),
+    'primary key' => array('revision_id'),
+  ));
+  // Populate the {custom_block} and {custom_block_revision} table.
+  $results = db_select('block_custom', 'bc')->fields('bc')->execute();
+
+  $uuid = new Uuid();
+  $execute = FALSE;
+  $block_insert = db_insert('custom_block')->fields(array(
+    'id',
+    'uuid',
+    'info',
+    'revision_id',
+    'langcode',
+    'type'
+  ));
+  $revision_insert = db_insert('custom_block_revision')->fields(array(
+    'id',
+    'revision_id',
+    'log',
+    'info'
+  ));
+  foreach ($results as $block) {
+    $custom_block = array(
+      'id' => $block->bid,
+      'uuid' => $uuid->generate(),
+      'info' => $block->info,
+      'revision_id' => $block->bid,
+      'langcode' => LANGUAGE_NOT_SPECIFIED,
+      'type' => 'basic'
+    );
+    $revision = array(
+      'id' => $block->bid,
+      'revision_id' => $block->bid,
+      'info' => $block->info,
+      'log' => 'Initial value from 7.x to 8.x upgrade'
+    );
+    $block_insert->values($custom_block);
+    $revision_insert->values($revision);
+    // We have something to execute.
+    $execute = TRUE;
+  }
+  if ($execute) {
+    $block_insert->execute();
+    $revision_insert->execute();
+  }
+}
+
+/**
+ * Migrate {block_custom}.body and {block_custom}.format to block_body field.
+ */
+function block_update_8008() {
+  $sandbox['#finished'] = 0;
+
+  if (!isset($sandbox['total'])) {
+    // Initial invocation.
+
+    // First, create the body field.
+    $body_field = array(
+      'field_name' => 'block_body',
+      'type' => 'text_with_summary',
+      'entity_types' => array('custom_block'),
+      'module' => 'text',
+      'cardinality' => 1,
+    );
+    _update_7000_field_create_field($body_field);
+
+    $instance = array(
+      'field_name' => 'block_body',
+      'entity_type' => 'custom_block',
+      'bundle' => 'basic',
+      'label' => 'Block body',
+      'widget' => array('type' => 'text_textarea_with_summary'),
+      'settings' => array('display_summary' => FALSE),
+    );
+    _update_7000_field_create_instance($body_field, $instance);
+
+    // Initialize state for future calls.
+    $sandbox['last'] = 0;
+    $sandbox['count'] = 0;
+
+    $query = db_select('block_custom', 'bc');
+    $sandbox['total'] = $query->countQuery()->execute()->fetchField();
+
+    $sandbox['body_field_id'] = $body_field['id'];
+  }
+  else {
+    // Subsequent invocations.
+
+    $found = FALSE;
+    if ($sandbox['total']) {
+      // Operate on each block in turn.
+      $batch_size = 200;
+      $query = db_select('block_custom', 'bc');
+      $query
+        ->fields('bc', array('bid', 'body', 'format'))
+        ->condition('bc.bid', $sandbox['last'], '>')
+        ->orderBy('bc.bid', 'ASC')
+        ->range(0, $batch_size);
+      $blocks = $query->execute();
+
+      // Load the block, set up 'body' and save the field data.
+      foreach ($blocks as $block) {
+        $found = TRUE;
+
+        $data = array(
+          LANGUAGE_NOT_SPECIFIED => array(
+            array(
+              'format' => $block->format,
+              'value' => $block->body
+            )
+          )
+        );
+        // This is a core update and no contrib modules are enabled yet, so
+        // we can assume default field storage for a faster update.
+        _update_7000_field_sql_storage_write('custom_block', 'basic', $block->bid, $block->bid, 'block_body', $data);
+
+        $sandbox['last'] = $block->bid;
+        $sandbox['count'] += 1;
+      }
+
+      $sandbox['#finished'] = min(0.99, $sandbox['count'] / $sandbox['total']);
+    }
+
+    if (!$found) {
+      // All blocks are processed.
+
+      // Remove the now-obsolete body info from block_custom.
+      db_drop_field('block_custom', 'body');
+      db_drop_field('block_custom', 'format');
+
+      // We're done.
+      $sandbox['#finished'] = 1;
+    }
+  }
+}
+
 /**
  * @} End of "addtogroup updates-7.x-to-8.x".
  * The next series of updates should start at 9000.
diff --git a/core/modules/block/custom_block/config/custom_block.type.basic.yml b/core/modules/block/custom_block/config/custom_block.type.basic.yml
new file mode 100644
index 0000000000000000000000000000000000000000..1b0e2f8dc5341bf92b06675112dd20bace46e673
--- /dev/null
+++ b/core/modules/block/custom_block/config/custom_block.type.basic.yml
@@ -0,0 +1,4 @@
+id: basic
+label: Basic block
+revision: '0'
+description: A basic block contains a title and a body.
diff --git a/core/modules/block/custom_block/custom_block.admin.inc b/core/modules/block/custom_block/custom_block.admin.inc
new file mode 100644
index 0000000000000000000000000000000000000000..3451e5dd794131e4138f9a4cb1f745bf295a8b6c
--- /dev/null
+++ b/core/modules/block/custom_block/custom_block.admin.inc
@@ -0,0 +1,98 @@
+<?php
+
+/**
+ * @file
+ * Admin page callbacks for the custom block module.
+ */
+
+use Drupal\custom_block\Plugin\Core\Entity\CustomBlockType;
+
+/**
+ * Page callback: Lists custom block types.
+ *
+ * @return array
+ *   A build array in the format expected by drupal_render().
+ *
+ * @see custom_block_menu()
+ */
+function custom_block_type_list() {
+  return drupal_container()->get('plugin.manager.entity')->getListController('custom_block_type')->render();
+}
+
+/**
+ * Page callback: Presents the custom block type creation form.
+ *
+ * @return array
+ *   A form array as expected by drupal_render().
+ *
+ * @see custom_block_menu()
+ */
+function custom_block_type_add() {
+  $block_type = entity_create('custom_block_type', array());
+  return entity_get_form($block_type);
+}
+
+/**
+ * Page callback: Presents the custom block type edit form.
+ *
+ * @param \Drupal\custom_block\Plugin\Core\Entity\CustomBlockType $block_type
+ *   The custom block type to edit.
+ *
+ * @return array
+ *   A form array as expected by drupal_render().
+ *
+ * @see custom_block_menu()
+ */
+function custom_block_type_edit(CustomBlockType $block_type) {
+  return entity_get_form($block_type);
+}
+
+/**
+ * Page callback: Form constructor for the custom block type deletion form.
+ *
+ * @param \Drupal\custom_block\Plugin\Core\Entity\CustomBlockType $block_type
+ *   The custom block type to be deleted.
+ *
+ * @see custom_block_menu()
+ * @see custom_block_type_delete_form_submit()
+ *
+ * @ingroup forms
+ */
+function custom_block_type_delete_form($form, &$form_state, CustomBlockType $block_type) {
+  $form_state['custom_block_type'] = $block_type;
+  $form['id'] = array(
+    '#type' => 'value',
+    '#value' => $block_type->id(),
+  );
+
+  $message = t('Are you sure you want to delete %label?', array('%label' => $block_type->label()));
+
+  $blocks = entity_query('custom_block')->condition('type', $block_type->id())->execute();
+  if (!empty($blocks)) {
+    drupal_set_title($message, PASS_THROUGH);
+    $caption = '<p>' . format_plural(count($blocks), '%label is used by 1 custom block on your site. You can not remove this block type until you have removed all of the %label blocks.', '%label is used by @count custom blocks on your site. You may not remove %label until you have removed all of the %label custom blocks.', array('%label' => $block_type->label())) . '</p>';
+    $form['description'] = array('#markup' => $caption);
+    return $form;
+  }
+
+  return confirm_form(
+    $form,
+    $message,
+    'admin/structure/custom-blocks',
+    t('This action cannot be undone.'),
+    t('Delete')
+  );
+}
+
+/**
+ * Form submission handler for custom_block_type_delete_form().
+ */
+function custom_block_type_delete_form_submit($form, &$form_state) {
+  $block_type = $form_state['custom_block_type'];
+  $block_type->delete();
+
+  drupal_set_message(t('Custom block type %label has been deleted.', array('%label' => $block_type->label())));
+  watchdog('custom_block', 'Custom block type %label has been deleted.', array('%label' => $block_type->label()), WATCHDOG_NOTICE);
+
+  $form_state['redirect'] = 'admin/structure/custom-blocks';
+}
diff --git a/core/modules/block/custom_block/custom_block.info b/core/modules/block/custom_block/custom_block.info
index f38e5402a9ce6193caac2f4e4c8a63f99757861a..295858e492476b8d178312d2a16baab2ac54745e 100644
--- a/core/modules/block/custom_block/custom_block.info
+++ b/core/modules/block/custom_block/custom_block.info
@@ -4,3 +4,4 @@ package = Core
 version = VERSION
 core = 8.x
 dependencies[] = block
+dependencies[] = text
diff --git a/core/modules/block/custom_block/custom_block.install b/core/modules/block/custom_block/custom_block.install
index 4a0e4b0162bfa28e5a8a6d2aff4eae097efc66aa..217cd5e5657ab18bf40a101cc52af0d8d5926ffb 100644
--- a/core/modules/block/custom_block/custom_block.install
+++ b/core/modules/block/custom_block/custom_block.install
@@ -10,21 +10,20 @@
  */
 function custom_block_schema() {
   $schema = array();
-  $schema['block_custom'] = array(
+  $schema['custom_block'] = array(
     'description' => 'Stores contents of custom-made blocks.',
     'fields' => array(
-      'bid' => array(
+      'id' => array(
         'type' => 'serial',
         'unsigned' => TRUE,
         'not null' => TRUE,
-        'description' => "The block's {block}.bid.",
+        'description' => "The block's {custom_block}.id.",
       ),
-      'body' => array(
-        'type' => 'text',
+      'uuid' => array(
+        'description' => 'Unique Key: Universally unique identifier for this entity.',
+        'type' => 'varchar',
+        'length' => 128,
         'not null' => FALSE,
-        'size' => 'big',
-        'description' => 'Block contents.',
-        'translatable' => TRUE,
       ),
       'info' => array(
         'type' => 'varchar',
@@ -33,17 +32,80 @@ function custom_block_schema() {
         'default' => '',
         'description' => 'Block description.',
       ),
-      'format' => array(
-        'type' => 'varchar',
-        'length' => 255,
+      // Defaults to NULL in order to avoid a brief period of potential
+      // deadlocks on the index.
+      'revision_id' => array(
+        'description' => 'The current {block_custom_revision}.revision_id version identifier.',
+        'type' => 'int',
+        'unsigned' => TRUE,
         'not null' => FALSE,
-        'description' => 'The {filter_format}.format of the block body.',
+        'default' => NULL,
+      ),
+      'type' => array(
+        'description' => 'The type of this custom block.',
+        'type' => 'varchar',
+        'length' => 32,
+        'not null' => TRUE,
+        'default' => '',
       ),
+      'langcode' => array(
+        'description' => 'The {language}.langcode of this node.',
+        'type' => 'varchar',
+        'length' => 12,
+        'not null' => TRUE,
+        'default' => '',
+      ),
+    ),
+    'primary key' => array('id'),
+    'indexes' => array(
+      'block_custom_type'   => array(array('type', 4)),
     ),
     'unique keys' => array(
+      'revision_id' => array('revision_id'),
+      'uuid' => array('uuid'),
       'info' => array('info'),
     ),
-    'primary key' => array('bid'),
+    'foreign keys' => array(
+      'custom_block_revision' => array(
+        'table' => 'custom_block_revision',
+        'columns' => array('revision_id' => 'revision_id'),
+      ),
+    ),
+  );
+
+  $schema['custom_block_revision'] = array(
+    'description' => 'Stores contents of custom-made blocks.',
+    'fields' => array(
+      'id' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => "The block's {custom_block}.id.",
+      ),
+      // Defaults to NULL in order to avoid a brief period of potential
+      // deadlocks on the index.
+      'revision_id' => array(
+        'description' => 'The current version identifier.',
+        'type' => 'serial',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+      ),
+      'log' => array(
+        'description' => 'The log entry explaining the changes in this version.',
+        'type' => 'text',
+        'not null' => TRUE,
+        'size' => 'big',
+      ),
+      'info' => array(
+        'type' => 'varchar',
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Block description.',
+      ),
+    ),
+    'primary key' => array('revision_id'),
   );
   return $schema;
 }
diff --git a/core/modules/block/custom_block/custom_block.js b/core/modules/block/custom_block/custom_block.js
new file mode 100644
index 0000000000000000000000000000000000000000..fb0905abc35e0bdac3d7d180f66f4ea225c77fe1
--- /dev/null
+++ b/core/modules/block/custom_block/custom_block.js
@@ -0,0 +1,46 @@
+/**
+ * @file
+ * Defines Javascript behaviors for the custom_block module.
+ */
+
+(function ($) {
+
+"use strict";
+
+Drupal.behaviors.customBlockDetailsSummaries = {
+  attach: function (context) {
+    var $context = $(context);
+    $context.find('.custom-block-form-revision-information').drupalSetSummary(function (context) {
+      var $context = $(context);
+      var revisionCheckbox = $context.find('.form-item-revision input');
+
+      // Return 'New revision' if the 'Create new revision' checkbox is checked,
+      // or if the checkbox doesn't exist, but the revision log does. For users
+      // without the "Administer content" permission the checkbox won't appear,
+      // but the revision log will if the content type is set to auto-revision.
+      if (revisionCheckbox.is(':checked') || (!revisionCheckbox.length && $context.find('.form-item-log textarea').length)) {
+        return Drupal.t('New revision');
+      }
+
+      return Drupal.t('No revision');
+    });
+
+    $context.find('fieldset.custom-block-translation-options').drupalSetSummary(function (context) {
+      var $context = $(context);
+      var translate;
+      var $checkbox = $context.find('.form-item-translation-translate input');
+
+      if ($checkbox.size()) {
+        translate = $checkbox.is(':checked') ? Drupal.t('Needs to be updated') : Drupal.t('Does not need to be updated');
+      }
+      else {
+        $checkbox = $context.find('.form-item-translation-retranslate input');
+        translate = $checkbox.is(':checked') ? Drupal.t('Flag other translations as outdated') : Drupal.t('Do not flag other translations as outdated');
+      }
+
+      return translate;
+    });
+  }
+};
+
+})(jQuery);
diff --git a/core/modules/block/custom_block/custom_block.module b/core/modules/block/custom_block/custom_block.module
index 8c7fdeae2918475bef5965e871271463b7dad2a3..83117c8607b7b879e50cbf208d215ec9627102d1 100644
--- a/core/modules/block/custom_block/custom_block.module
+++ b/core/modules/block/custom_block/custom_block.module
@@ -5,29 +5,129 @@
  * Allows the creaation of custom blocks through the user interface.
  */
 
+use Drupal\custom_block\Plugin\Core\Entity\CustomBlockType;
+use Drupal\custom_block\Plugin\Core\Entity\CustomBlock;
+use Drupal\custom_block\Plugin\block\block\CustomBlockBlock;
+use Drupal\block\Plugin\Core\Entity\Block;
+
+/**
+ * Implements hook_menu_local_task_alter().
+ */
+function custom_block_menu_local_tasks_alter(&$data, $router_item, $root_path) {
+  // Make sure we're at least in the right general area.
+  if (substr($root_path, 0, 26) == 'admin/structure/block/list') {
+    // This is the path we want to match.
+    $admin_path = array(
+      'admin',
+      'structure',
+      'block',
+      'list',
+      'add',
+    );
+    // We're going to want an array so we can remove the plugin id.
+    $path_map = explode('/', $root_path);
+    $before = array_slice($path_map, 0, 4);
+    $after = array_slice($path_map, 5);
+    $path_map = array_merge($before, $after);
+    // If the path is absolutely equal to $admin_path, we have a winner.
+    if($path_map === $admin_path) {
+      $item = menu_get_item('block/add');
+      if ($item['access']) {
+        $data['actions']['output'][] = array(
+          '#theme' => 'menu_local_action',
+          '#link' => $item,
+        );
+      }
+    }
+  }
+}
+
 /**
  * Implements hook_menu().
  */
 function custom_block_menu() {
   $items = array();
-  // Add an "Add custom block" action link for each theme.
-  foreach (drupal_container()->get('plugin.manager.system.plugin_ui')->getDefinitions() as $plugin_id => $plugin) {
-    // Only add the link for block plugin UI derivatives (that is, per-theme
-    // block instance configuration).
-    if (strpos($plugin_id, 'block_plugin_ui') === 0) {
-      list(, $theme) = explode(':', $plugin_id);
-      $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' => 'block_admin_add',
-        'page arguments' => array('custom_block:custom_block', $theme),
-        'access callback' => TRUE,
-        'type' => MENU_LOCAL_ACTION,
-        'file' => 'block.admin.inc',
-        'file path' => drupal_get_path('module', 'block'),
-      );
-    }
-  }
+  $items['admin/structure/custom-blocks'] = array(
+    'title' => 'Custom block types',
+    'description' => 'Manage custom block types.',
+    'page callback' => 'custom_block_type_list',
+    'access arguments' => array('administer blocks'),
+    'file' => 'custom_block.admin.inc',
+  );
+  $items['admin/structure/custom-blocks/add'] = array(
+    'title' => 'Add custom block type',
+    'page callback' => 'custom_block_type_add',
+    'access arguments' => array('administer blocks'),
+    'type' => MENU_LOCAL_ACTION,
+    'weight' => 1,
+    'file' => 'custom_block.admin.inc',
+  );
+  $items['admin/structure/custom-blocks/manage/%custom_block_type'] = array(
+    'title' => 'Edit custom block type',
+    'title callback' => 'entity_page_label',
+    'title arguments' => array(4),
+    'page callback' => 'custom_block_type_edit',
+    'page arguments' => array(4),
+    'access arguments' => array('administer blocks'),
+    'file' => 'custom_block.admin.inc',
+  );
+  $items['admin/structure/custom-blocks/manage/%custom_block_type/edit'] = array(
+    'title' => 'Edit',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['admin/structure/custom-blocks/manage/%custom_block_type/delete'] = array(
+    'title' => 'Delete',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('custom_block_type_delete_form', 4),
+    'access arguments' => array('administer blocks'),
+    'type' => MENU_LOCAL_TASK,
+    'weight' => 10,
+    'file' => 'custom_block.admin.inc',
+  );
+
+  $items['block/add'] = array(
+    'title' => 'Add custom block',
+    'page callback' => 'custom_block_add_page',
+    'access arguments' => array('administer blocks'),
+    'file' => 'custom_block.pages.inc',
+  );
+
+  $items['block/add/%custom_block_type'] = array(
+    'title callback' => 'entity_page_label',
+    'title arguments' => array(2),
+    'page callback' => 'custom_block_add',
+    'page arguments' => array(2),
+    'access arguments' => array('administer blocks'),
+    'description' => 'Add custom block',
+    'file' => 'custom_block.pages.inc',
+  );
+  // There has to be a base-item in order for contextual links to work.
+  $items['block/%custom_block'] = array(
+    'title' => 'Edit',
+    'page callback' => 'custom_block_edit',
+    'page arguments' => array(1),
+    'access callback' => 'entity_page_access',
+    'access arguments' => array(1, 'update'),
+    'file' => 'custom_block.pages.inc',
+  );
+  $items['block/%custom_block/edit'] = array(
+    'title' => 'Edit',
+    'weight' => 0,
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+  );
+  $items['block/%custom_block/delete'] = array(
+    'title' => 'Delete',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('custom_block_delete_form', 1),
+    'access callback' => 'entity_page_access',
+    'access arguments' => array(1, 'delete'),
+    'weight' => 1,
+    'type' => MENU_LOCAL_TASK,
+    'context' => MENU_CONTEXT_INLINE,
+    'file' => 'custom_block.pages.inc',
+  );
   return $items;
 }
 
@@ -39,6 +139,10 @@ function custom_block_theme($existing, $type, $theme, $path) {
     'custom_block_block' => array(
       'variables' => array('body' => NULL, 'format' => NULL),
     ),
+    'custom_block_add_list' => array(
+      'variables' => array('content' => NULL),
+      'file' => 'custom_block.pages.inc',
+    ),
   );
 }
 
@@ -53,3 +157,161 @@ function theme_custom_block_block($variables) {
 
   return check_markup($body, $format);
 }
+
+/**
+ * Loads a custom block type.
+ *
+ * @param int $id
+ *   The ID of the custom block type to load.
+ *
+ * @return Drupal\custom_block\Plugin\Core\Entity\CustomBlockType|false
+ *   A CustomBlockType object or FALSE if the requested $id does not exist.
+ */
+function custom_block_type_load($id) {
+  return entity_load('custom_block_type', $id);
+}
+
+/**
+ * Loads a custom block.
+ *
+ * @param int $id
+ *   The id of the custom block.
+ *
+ * @return Drupal\custom_block\Plugin\Core\Entity\CustomBlock|false
+ *   A CustomBlock object or FALSE if the requested $id does not exist.
+ */
+function custom_block_load($id) {
+  return entity_load('custom_block', $id);
+}
+
+/**
+ * Implements hook_entity_info_alter().
+ */
+function custom_block_entity_info_alter(&$types) {
+  // Add a translation handler for fields if the language module is enabled.
+  if (module_exists('language')) {
+    $types['custom_block']['translation']['custom_block'] = TRUE;
+  }
+}
+
+/**
+ * Implements hook_entity_bundle_info().
+ */
+function custom_block_entity_bundle_info() {
+  $bundles = array();
+  foreach (config_get_storage_names_with_prefix('custom_block.type.') as $config_name) {
+    $config = config($config_name);
+    $bundles['custom_block'][$config->get('id')] = array(
+      'label' => $config->get('label'),
+      'admin' => array(
+        'path' => 'admin/structure/custom-blocks/manage/%',
+        'real path' => 'admin/structure/custom-blocks/manage/' . $config->get('id'),
+        'bundle argument' => 4,
+      ),
+    );
+  }
+  return $bundles;
+}
+
+/**
+ * Implements hook_entity_view_mode_info().
+ */
+function custom_block_entity_view_mode_info() {
+  $view_modes['custom_block']['full'] = array(
+    'label' => t('Full'),
+  );
+  return $view_modes;
+}
+
+/**
+ * Adds the default body field to a custom block type.
+ *
+ * @param string $block_type_id
+ *   Id of the block type.
+ * @param string $label
+ *   (optional) The label for the body instance. Defaults to 'Block body'
+ *
+ * @return array()
+ *   Body field instance.
+ */
+function custom_block_add_body_field($block_type_id, $label = 'Block body') {
+  // Add or remove the body field, as needed.
+  $field = field_info_field('block_body');
+  $instance = field_info_instance('custom_block', 'block_body', $block_type_id);
+  if (empty($field)) {
+    $field = array(
+      'field_name' => 'block_body',
+      'type' => 'text_with_summary',
+      'entity_types' => array('custom_block'),
+    );
+    $field = field_create_field($field);
+  }
+  if (empty($instance)) {
+    $instance = array(
+      'field_name' => 'block_body',
+      'entity_type' => 'custom_block',
+      'bundle' => $block_type_id,
+      'label' => $label,
+      'widget' => array('type' => 'text_textarea_with_summary'),
+      'settings' => array('display_summary' => FALSE),
+    );
+    $instance = field_create_instance($instance);
+
+    // Assign display settings for 'default' view mode.
+    entity_get_display('custom_block', $block_type_id, 'default')
+      ->setComponent('block_body', array(
+        'label' => 'hidden',
+        'type' => 'text_default',
+      ))
+      ->save();
+  }
+
+  return $instance;
+}
+
+/**
+ * Implements hook_form_FORM_ID_alter() for block_plugin_ui().
+ */
+function custom_block_form_block_plugin_ui_alter(&$form, $form_state) {
+  foreach ($form['left']['plugin_library']['#rows'] as $plugin_id => &$row) {
+    // @todo Clean up when http://drupal.org/node/1874498 lands.
+    if (strpos($plugin_id, ':') === FALSE) {
+      continue;
+    }
+    list($base, $derivative) = explode(':', $plugin_id);
+    if ($base !== 'custom_block') {
+      continue;
+    }
+    $custom_block = entity_load_by_uuid('custom_block', $derivative);
+    $row['1']['data']['#links']['edit'] = array(
+      'title' => t('Edit'),
+      'href' => 'block/' . $custom_block->id() . '/edit'
+    );
+  }
+}
+
+/**
+ * Implements hook_admin_paths().
+ */
+function custom_block_admin_paths() {
+  $paths = array(
+    'block/add' => TRUE,
+    'block/add/*' => TRUE,
+    'block/*/edit' => TRUE,
+    'block/*/delete' => TRUE,
+    'admin/structure/custom-blocks/*' => TRUE,
+  );
+  return $paths;
+}
+
+/**
+ * Implements hook_block_view_alter().
+ */
+function custom_block_block_view_alter(array &$build, Block $block) {
+  // Add contextual links for custom blocks.
+  if ($block->getPlugin() instanceof CustomBlockBlock) {
+    // Move contextual links from inner content to outer wrapper.
+    $build['#contextual_links']['custom_block'] = $build['content']['#contextual_links']['custom_block'];
+    unset($build['content']['#contextual_links']['custom_block']);
+  }
+}
diff --git a/core/modules/block/custom_block/custom_block.pages.inc b/core/modules/block/custom_block/custom_block.pages.inc
new file mode 100644
index 0000000000000000000000000000000000000000..439030ee3d5cb055c017b2b6767988fcbd2f7d25
--- /dev/null
+++ b/core/modules/block/custom_block/custom_block.pages.inc
@@ -0,0 +1,153 @@
+<?php
+
+/**
+ * @file
+ * Provides page callbacks for custom blocks.
+ */
+
+use Drupal\custom_block\Plugin\Core\Entity\CustomBlockType;
+use Drupal\custom_block\Plugin\Core\Entity\CustomBlock;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+
+/**
+ * Page callback: Displays add custom block links for available types.
+ *
+ * @return array
+ *   A render array for a list of the custom block types that can be added or
+ *   if there is only one custom block type defined for the site, the function
+ *   returns the custom block add page for that custom block type.
+ *
+ * @see custom_block_menu()
+ */
+function custom_block_add_page() {
+  $options = array();
+  $request = drupal_container()->get('request');
+  if (($theme = $request->attributes->get('theme')) && in_array($theme, array_keys(list_themes()))) {
+    // We have navigated to this page from the block library and will keep track
+    // of the theme for redirecting the user to the configuration page for the
+    // newly created block in the given theme.
+    $options = array(
+      'query' => array('theme' => $theme)
+    );
+  }
+  $types = entity_load_multiple('custom_block_type');
+  if ($types && count($types) == 1) {
+    $type = reset($types);
+    return custom_block_add($type);
+  }
+
+  return array('#theme' => 'custom_block_add_list', '#content' => $types);
+}
+
+/**
+ * Returns HTML for a list of available custom block types for block creation.
+ *
+ * @param $variables
+ *   An associative array containing:
+ *   - content: An array of block types.
+ *
+ * @see custom_block_add_page()
+ *
+ * @ingroup themeable
+ */
+function theme_custom_block_add_list($variables) {
+  $content = $variables['content'];
+  $output = '';
+
+  if ($content) {
+    $output = '<dl class="node-type-list">';
+    foreach ($content as $type) {
+      $output .= '<dt>' . l($type->label(), 'block/add/' . $type->id()) . '</dt>';
+      $output .= '<dd>' . filter_xss_admin($type->description) . '</dd>';
+    }
+    $output .= '</dl>';
+  }
+  return $output;
+}
+
+
+/**
+ * Page callback: Presents the custom block creation form.
+ *
+ * @param Drupal\custom_block\Plugin\Core\Entity\CustomBlockType $block_type
+ *   The custom block type to add.
+ *
+ * @return array
+ *   A form array as expected by drupal_render().
+ *
+ * @see custom_block_menu()
+ */
+function custom_block_add(CustomBlockType $block_type) {
+  drupal_set_title(t('Add %type custom block', array(
+    '%type' => $block_type->label()
+  )), PASS_THROUGH);
+  $block = entity_create('custom_block', array(
+    'type' => $block_type->id()
+  ));
+  $options = array();
+  $request = drupal_container()->get('request');
+  if (($theme = $request->attributes->get('theme')) && in_array($theme, array_keys(list_themes()))) {
+    // We have navigated to this page from the block library and will keep track
+    // of the theme for redirecting the user to the configuration page for the
+    // newly created block in the given theme.
+    $block->setTheme($theme);
+  }
+  return entity_get_form($block);
+}
+
+/**
+ * Page callback: Presents the custom block edit form.
+ *
+ * @param Drupal\custom_block\Plugin\Core\Entity\CustomBlock $block
+ *   The custom block to edit.
+ *
+ * @return array
+ *   A form array as expected by drupal_render().
+ *
+ * @see custom_block_menu()
+ */
+function custom_block_edit(CustomBlock $block) {
+  drupal_set_title(t('Edit custom block %label', array('%label' => $block->label())), PASS_THROUGH);
+  return entity_get_form($block);
+}
+
+/**
+ * Page callback: Form constructor for the custom block deletion form.
+ *
+ * @param Drupal\custom_block\Plugin\Core\Entity\CustomBlock $block
+ *   The custom block to be deleted.
+ *
+ * @see custom_block_menu()
+ * @see custom_block_delete_form_submit()
+ *
+ * @ingroup forms
+ */
+function custom_block_delete_form($form, &$form_state, CustomBlock $block) {
+  $form_state['custom_block'] = $block;
+  $form['id'] = array(
+    '#type' => 'value',
+    '#value' => $block->id(),
+  );
+
+  return confirm_form(
+    $form,
+    t('Are you sure you want to delete %label?', array('%label' => $block->label())),
+    'admin/structure/block',
+    t('This action cannot be undone.'),
+    t('Delete')
+  );
+}
+
+/**
+ * Form submission handler for custom_block_delete_form().
+ */
+function custom_block_delete_form_submit($form, &$form_state) {
+  // @todo Delete all configured instances of the block.
+  $block = $form_state['custom_block'];
+  $block->delete();
+
+  drupal_set_message(t('Custom block %label has been deleted.', array('%label' => $block->label())));
+  watchdog('custom_block', 'Custom block %label has been deleted.', array('%label' => $block->label()), WATCHDOG_NOTICE);
+
+  $form_state['redirect'] = 'admin/structure/block';
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockAccessController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockAccessController.php
new file mode 100644
index 0000000000000000000000000000000000000000..e9c1e3106803322ed86702ab7600877da01e3887
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockAccessController.php
@@ -0,0 +1,47 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\CustomBlockAccessController.
+ */
+
+namespace Drupal\custom_block;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\user\Plugin\Core\Entity\User;
+use Drupal\Core\Entity\EntityAccessControllerInterface;
+
+/**
+ * Defines the access controller for the custom block entity type.
+ */
+class CustomBlockAccessController implements EntityAccessControllerInterface {
+
+  /**
+   * Implements EntityAccessControllerInterface::viewAccess().
+   */
+  public function viewAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
+    return user_access('view content', $account);
+  }
+
+  /**
+   * Implements EntityAccessControllerInterface::createAccess().
+   */
+  public function createAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
+    return user_access('administer blocks', $account);
+  }
+
+  /**
+   * Implements EntityAccessControllerInterface::updateAccess().
+   */
+  public function updateAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
+    return user_access('administer blocks', $account);
+  }
+
+  /**
+   * Implements EntityAccessControllerInterface::deleteAccess().
+   */
+  public function deleteAccess(EntityInterface $entity, $langcode = LANGUAGE_DEFAULT, User $account = NULL) {
+    return user_access('administer blocks', $account);
+  }
+
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockFormController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockFormController.php
new file mode 100644
index 0000000000000000000000000000000000000000..bec8b59a3ef51f90975825083d6da3517b76b5d6
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockFormController.php
@@ -0,0 +1,216 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\CustomBlockFormController.
+ */
+
+namespace Drupal\custom_block;
+
+use Drupal\Core\Datetime\DrupalDateTime;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityFormControllerNG;
+
+/**
+ * Form controller for the custom block edit forms.
+ */
+class CustomBlockFormController extends EntityFormControllerNG {
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::prepareEntity().
+   *
+   * Prepares the custom block object.
+   *
+   * Fills in a few default values, and then invokes hook_custom_block_prepare()
+   * on all modules.
+   */
+  protected function prepareEntity(EntityInterface $block) {
+    // Set up default values, if required.
+    $block_type = entity_load('custom_block_type', $block->type->value);
+    // If this is a new custom block, fill in the default values.
+    if (isset($block->id->value)) {
+      $block->set('log', NULL);
+    }
+    // Always use the default revision setting.
+    $block->setNewRevision($block_type->revision);
+
+    module_invoke_all('custom_block_prepare', $block);
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::form().
+   */
+  public function form(array $form, array &$form_state, EntityInterface $block) {
+    // Override the default CSS class name, since the user-defined custom block
+    // type name in 'TYPE-block-form' potentially clashes with third-party class
+    // names.
+    $form['#attributes']['class'][0] = drupal_html_class('block-' . $block->type->value . '-form');
+
+    // Basic block information.
+    // These elements are just values so they are not even sent to the client.
+    foreach (array('revision_id', 'id') as $key) {
+      $form[$key] = array(
+        '#type' => 'value',
+        '#value' => $block->$key->value,
+      );
+    }
+
+    $form['info'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Block description'),
+      '#required' => TRUE,
+      '#default_value' => $block->info->value,
+      '#weight' => -5,
+      '#description' => t('A brief description of your block. Used on the <a href="@overview">Blocks administration page</a>.', array('@overview' => url('admin/structure/block'))),
+    );
+
+    $language_configuration = module_invoke('language', 'get_default_configuration', 'custom_block', $block->type->value);
+
+    // Set the correct default language.
+    if ($block->isNew() && !empty($language_configuration['langcode'])) {
+      $language_default = language($language_configuration['langcode']);
+      $block->langcode->value = $language_default->langcode;
+    }
+
+    $form['langcode'] = array(
+      '#title' => t('Language'),
+      '#type' => 'language_select',
+      '#default_value' => $block->langcode->value,
+      '#languages' => LANGUAGE_ALL,
+      '#access' => isset($language_configuration['language_show']) && $language_configuration['language_show'],
+    );
+
+    $form['additional_settings'] = array(
+      '#type' => 'vertical_tabs',
+      '#weight' => 99,
+    );
+
+    // Add a log field if the "Create new revision" option is checked, or if the
+    // current user has the ability to check that option.
+    $form['revision_information'] = array(
+      '#type' => 'details',
+      '#title' => t('Revision information'),
+      '#collapsible' => TRUE,
+      // Collapsed by default when "Create new revision" is unchecked.
+      '#collapsed' => !$block->isNewRevision(),
+      '#group' => 'additional_settings',
+      '#attributes' => array(
+        'class' => array('custom-block-form-revision-information'),
+      ),
+      '#attached' => array(
+        'js' => array(drupal_get_path('module', 'custom_block') . '/custom_block.js'),
+      ),
+      '#weight' => 20,
+      '#access' => $block->isNewRevision() || user_access('administer blocks'),
+    );
+
+    $form['revision_information']['revision'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Create new revision'),
+      '#default_value' => $block->isNewRevision(),
+      '#access' => user_access('administer blocks'),
+    );
+
+    // Check the revision log checkbox when the log textarea is filled in.
+    // This must not happen if "Create new revision" is enabled by default,
+    // since the state would auto-disable the checkbox otherwise.
+    if (!$block->isNewRevision()) {
+      $form['revision_information']['revision']['#states'] = array(
+        'checked' => array(
+          'textarea[name="log"]' => array('empty' => FALSE),
+        ),
+      );
+    }
+
+    $form['revision_information']['log'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Revision log message'),
+      '#rows' => 4,
+      '#default_value' => $block->log->value,
+      '#description' => t('Briefly describe the changes you have made.'),
+    );
+
+    return parent::form($form, $form_state, $block);
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::submit().
+   *
+   * Updates the custom block object by processing the submitted values.
+   *
+   * This function can be called by a "Next" button of a wizard to update the
+   * form state's entity with the current step's values before proceeding to the
+   * next step.
+   */
+  public function submit(array $form, array &$form_state) {
+    // Build the block object from the submitted values.
+    $block = parent::submit($form, $form_state);
+
+    // Save as a new revision if requested to do so.
+    if (!empty($form_state['values']['revision'])) {
+      $block->setNewRevision();
+    }
+
+    return $block;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::save().
+   */
+  public function save(array $form, array &$form_state) {
+    $block = $this->getEntity($form_state);
+    $insert = empty($block->id->value);
+    $block->save();
+    $watchdog_args = array('@type' => $block->bundle(), '%info' => $block->label());
+    $block_type = entity_load('custom_block_type', $block->type->value);
+    $t_args = array('@type' => $block_type->label(), '%info' => $block->label());
+
+    if ($insert) {
+      watchdog('content', '@type: added %info.', $watchdog_args, WATCHDOG_NOTICE);
+      drupal_set_message(t('@type %info has been created.', $t_args));
+    }
+    else {
+      watchdog('content', '@type: updated %info.', $watchdog_args, WATCHDOG_NOTICE);
+      drupal_set_message(t('@type %info has been updated.', $t_args));
+    }
+
+    if ($block->id->value) {
+      $form_state['values']['id'] = $block->id->value;
+      $form_state['id'] = $block->id->value;
+      if ($insert) {
+        if ($theme = $block->getTheme()) {
+          $form_state['redirect'] = 'admin/structure/block/add/custom_block:' . $block->uuid->value . '/' . $theme;
+        }
+        else {
+          $form_state['redirect'] = 'admin/structure/block/add/custom_block:' . $block->uuid->value . '/' . variable_get('theme_default', 'stark');
+        }
+      }
+      else {
+        $form_state['redirect'] = 'admin/structure/block';
+      }
+    }
+    else {
+      // In the unlikely case something went wrong on save, the block will be
+      // rebuilt and block form redisplayed.
+      drupal_set_message(t('The block could not be saved.'), 'error');
+      $form_state['rebuild'] = TRUE;
+    }
+
+    // Clear the page and block caches.
+    cache_invalidate_tags(array('content' => TRUE));
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::delete().
+   */
+  public function delete(array $form, array &$form_state) {
+    $destination = array();
+    if (isset($_GET['destination'])) {
+      $destination = drupal_get_destination();
+      unset($_GET['destination']);
+    }
+    $block = $this->buildEntity($form, $form_state);
+    $form_state['redirect'] = array('block/' . $block->uuid->value . '/delete', array('query' => $destination));
+  }
+
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockRenderController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockRenderController.php
new file mode 100644
index 0000000000000000000000000000000000000000..00fa8ec6242e3896d201bde0260600bbdc25a5b7
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockRenderController.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\CustomBlockRenderController.
+ */
+
+namespace Drupal\custom_block;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityRenderController;
+use Drupal\entity\Plugin\Core\Entity\EntityDisplay;
+
+/**
+ * Render controller for custom blocks.
+ */
+class CustomBlockRenderController extends EntityRenderController {
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityRenderController::alterBuild().
+   */
+  protected function alterBuild(array &$build, EntityInterface $entity, EntityDisplay $display, $view_mode, $langcode = NULL) {
+    parent::alterBuild($build, $entity, $display, $view_mode, $langcode);
+    // Add contextual links for this custom block.
+    if (!empty($entity->id->value) && $view_mode == 'full') {
+      $build['#contextual_links']['custom_block'] = array('block', array($entity->id()));
+    }
+  }
+
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php
new file mode 100644
index 0000000000000000000000000000000000000000..87ff2d54b072308592d1f903b9fe3251fda9eec6
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php
@@ -0,0 +1,118 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\CustomBlockStorageController.
+ */
+
+namespace Drupal\custom_block;
+
+use Drupal\Core\Entity\DatabaseStorageControllerNG;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Controller class for custom blocks.
+ *
+ * This extends the Drupal\Core\Entity\DatabaseStorageController class, adding
+ * required special handling for custom block entities.
+ */
+class CustomBlockStorageController extends DatabaseStorageControllerNG {
+
+  /**
+   * Overrides \Drupal\Core\Entity\DatabaseStorageController::preSaveRevision().
+   */
+  protected function preSaveRevision(\stdClass $record, EntityInterface $entity) {
+    if ($entity->isNewRevision()) {
+      // When inserting either a new custom block or a new custom_block
+      // revision, $entity->log must be set because {block_custom_revision}.log
+      // is a text column and therefore cannot have a default value. However,
+      // it might not be set at this point (for example, if the user submitting
+      // the form does not have permission to create revisions), so we ensure
+      // that it is at least an empty string in that case.
+      // @todo: Make the {block_custom_revision}.log column nullable so that we
+      // can remove this check.
+      if (!isset($record->log)) {
+        $record->log = '';
+      }
+    }
+    elseif (!isset($record->log) || $record->log === '') {
+      // If we are updating an existing custom_block without adding a new
+      // revision, we need to make sure $entity->log is unset whenever it is
+      // empty. As long as $entity->log is unset, drupal_write_record() will not
+      // attempt to update the existing database column when re-saving the
+      // revision; therefore, this code allows us to avoid clobbering an
+      // existing log entry with an empty one.
+      unset($record->log);
+    }
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\DatabaseStorageController::attachLoad().
+   */
+  protected function attachLoad(&$blocks, $load_revision = FALSE) {
+    // Create an array of block types for passing as a load argument.
+    // Note that blocks at this point are still \StdClass objects returned from
+    // the database.
+    foreach ($blocks as $id => $entity) {
+      $types[$entity->type] = $entity->type;
+    }
+
+    // Besides the list of blocks, pass one additional argument to
+    // hook_custom_block_load(), containing a list of block types that were
+    // loaded.
+    $this->hookLoadArguments = array($types);
+    parent::attachLoad($blocks, $load_revision);
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\DatabaseStorageController::postSave().
+   */
+  protected function postSave(EntityInterface $block, $update) {
+    // Invalidate the block cache to update custom block-based derivatives.
+    drupal_container()->get('plugin.manager.block')->clearCachedDefinitions();
+  }
+
+  /**
+   * Implements \Drupal\Core\Entity\DataBaseStorageControllerNG::basePropertyDefinitions().
+   */
+  public function baseFieldDefinitions() {
+    $properties['id'] = array(
+      'label' => t('ID'),
+      'description' => t('The custom block ID.'),
+      'type' => 'integer_field',
+      'read-only' => TRUE,
+    );
+    $properties['uuid'] = array(
+      'label' => t('UUID'),
+      'description' => t('The custom block UUID.'),
+      'type' => 'string_field',
+    );
+    $properties['revision_id'] = array(
+      'label' => t('Revision ID'),
+      'description' => t('The revision ID.'),
+      'type' => 'integer_field',
+    );
+    $properties['langcode'] = array(
+      'label' => t('Language code'),
+      'description' => t('The comment language code.'),
+      'type' => 'language_field',
+    );
+    $properties['info'] = array(
+      'label' => t('Subject'),
+      'description' => t('The custom block name.'),
+      'type' => 'string_field',
+    );
+    $properties['type'] = array(
+      'label' => t('Block type'),
+      'description' => t('The block type.'),
+      'type' => 'string_field',
+    );
+    $properties['log'] = array(
+      'label' => t('Revision log message'),
+      'description' => t('The revision log message.'),
+      'type' => 'string_field',
+    );
+    return $properties;
+  }
+
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTranslationController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTranslationController.php
new file mode 100644
index 0000000000000000000000000000000000000000..a8848c7697097daa4d58998f13d17a5f512e99a3
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTranslationController.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\CustomBlockTranslationController.
+ */
+
+namespace Drupal\custom_block;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\translation_entity\EntityTranslationControllerNG;
+
+/**
+ * Defines the translation controller class for custom blocks.
+ */
+class CustomBlockTranslationController extends EntityTranslationControllerNG {
+
+  /**
+   * Overrides EntityTranslationController::getAccess().
+   */
+  public function getAccess(EntityInterface $entity, $op) {
+    return user_access('administer blocks');
+  }
+
+  /**
+   * Overrides EntityTranslationController::entityFormAlter().
+   */
+  public function entityFormAlter(array &$form, array &$form_state, EntityInterface $entity) {
+    parent::entityFormAlter($form, $form_state, $entity);
+    // Move the translation fieldset to a vertical tab.
+    if (isset($form['translation'])) {
+      $form['translation'] += array(
+        '#group' => 'additional_settings',
+        '#weight' => 100,
+        '#attributes' => array(
+          'class' => array('custom-block-translation-options'),
+        ),
+      );
+    }
+  }
+
+  /**
+   * Overrides EntityTranslationController::entityFormTitle().
+   */
+  protected function entityFormTitle(EntityInterface $entity) {
+    $block_type = entity_load('custom_block_type', $entity->type->value);
+    return t('<em>Edit @type</em> @title', array('@type' => $block_type->label(), '@title' => $entity->label()));
+  }
+
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeFormController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeFormController.php
new file mode 100644
index 0000000000000000000000000000000000000000..77664e9b80475a035c14d70cad4c597685653f8b
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeFormController.php
@@ -0,0 +1,114 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\CustomBlockTypeFormController.
+ */
+
+namespace Drupal\custom_block;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityFormController;
+
+/**
+ * Base form controller for category edit forms.
+ */
+class CustomBlockTypeFormController extends EntityFormController {
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::form().
+   */
+  public function form(array $form, array &$form_state, EntityInterface $block_type) {
+    $form = parent::form($form, $form_state, $block_type);
+
+    $form['label'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Label'),
+      '#maxlength' => 255,
+      '#default_value' => $block_type->label(),
+      '#description' => t("Provide a label for this block type to help identify it in the administration pages."),
+      '#required' => TRUE,
+    );
+    $form['id'] = array(
+      '#type' => 'machine_name',
+      '#default_value' => $block_type->id(),
+      '#machine_name' => array(
+        'exists' => 'custom_block_type_load',
+      ),
+      '#disabled' => !$block_type->isNew(),
+    );
+
+    $form['description'] = array(
+      '#type' => 'textarea',
+      '#default_value' => $block_type->description,
+      '#description' => t('Enter a description for this block type.'),
+      '#title' => t('Description'),
+    );
+
+    $form['revision'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Create new revision'),
+      '#default_value' => $block_type->revision,
+      '#description' => t('Create a new revision by default for this block type.')
+    );
+
+    if (module_exists('translation_entity')) {
+      $form['language'] = array(
+        '#type' => 'details',
+        '#title' => t('Language settings'),
+        '#collapsible' => TRUE,
+        '#collapsed' => TRUE,
+        '#group' => 'additional_settings',
+      );
+
+      $language_configuration = language_get_default_configuration('custom_block', $block_type->id());
+      $form['language']['language_configuration'] = array(
+        '#type' => 'language_configuration',
+        '#entity_information' => array(
+          'entity_type' => 'custom_block',
+          'bundle' => $block_type->id(),
+        ),
+        '#default_value' => $language_configuration,
+      );
+
+      $form['#submit'][] = 'language_configuration_element_submit';
+    }
+
+    $form['actions'] = array('#type' => 'actions');
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save'),
+    );
+
+    return $form;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::save().
+   */
+  public function save(array $form, array &$form_state) {
+    $block_type = $this->getEntity($form_state);
+    $status = $block_type->save();
+
+    $uri = $block_type->uri();
+    if ($status == SAVED_UPDATED) {
+      drupal_set_message(t('Custom block type %label has been updated.', array('%label' => $block_type->label())));
+      watchdog('custom_block', 'Custom block type %label has been updated.', array('%label' => $block_type->label()), WATCHDOG_NOTICE, l(t('Edit'), $uri['path'] . '/edit'));
+    }
+    else {
+      drupal_set_message(t('Custom block type %label has been added.', array('%label' => $block_type->label())));
+      watchdog('custom_block', 'Custom block type %label has been added.', array('%label' => $block_type->label()), WATCHDOG_NOTICE, l(t('Edit'), $uri['path'] . '/edit'));
+    }
+
+    $form_state['redirect'] = 'admin/structure/custom-blocks';
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityFormController::delete().
+   */
+  public function delete(array $form, array &$form_state) {
+    $block_type = $this->getEntity($form_state);
+    $form_state['redirect'] = 'admin/structure/custom-blocks/manage/' . $block_type->id() . '/delete';
+  }
+
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeListController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeListController.php
new file mode 100644
index 0000000000000000000000000000000000000000..8ef270c5f8040c1d4eb4d4127affccc509b180d6
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeListController.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\CustomBlockTypeListController.
+ */
+
+namespace Drupal\custom_block;
+
+use Drupal\Core\Config\Entity\ConfigEntityListController;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Provides a listing of custom block types.
+ */
+class CustomBlockTypeListController extends ConfigEntityListController {
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityListController::getOperations().
+   */
+  public function getOperations(EntityInterface $entity) {
+    $operations = parent::getOperations($entity);
+    if (module_exists('field_ui')) {
+      $uri = $entity->uri();
+      $operations['manage-fields'] = array(
+        'title' => t('Manage fields'),
+        'href' => $uri['path'] . '/fields',
+        'options' => $uri['options'],
+        'weight' => 15,
+      );
+      $operations['manage-display'] = array(
+        'title' => t('Manage display'),
+        'href' => $uri['path'] . '/display',
+        'options' => $uri['options'],
+        'weight' => 20,
+      );
+    }
+    return $operations;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityListController::buildHeader().
+   */
+  public function buildHeader() {
+    $row['type'] = t('Block type');
+    $row['description'] = t('Description');
+    $row['operations'] = t('Operations');
+    return $row;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityListController::buildRow().
+   */
+  public function buildRow(EntityInterface $entity) {
+    parent::buildRow($entity);
+    $uri = $entity->uri();
+    $row['type'] = l($entity->label(), $uri['path'], $uri['options']);
+    $row['description'] = filter_xss_admin($entity->description);
+    $row['operations']['data'] = $this->buildOperations($entity);
+    return $row;
+  }
+
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeStorageController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeStorageController.php
new file mode 100644
index 0000000000000000000000000000000000000000..1c8cbc2715a94e249655b0104e486a4fe19b377b
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeStorageController.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\CustomBlockTypeStorageController.
+ */
+
+namespace Drupal\custom_block;
+
+use Drupal\Core\Config\Entity\ConfigStorageController;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Controller class for custom block types.
+ */
+class CustomBlockTypeStorageController extends ConfigStorageController {
+
+  /**
+   * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::postSave().
+   */
+  protected function postSave(EntityInterface $entity, $update) {
+    parent::postSave($entity, $update);
+
+    if (!$update) {
+      field_attach_create_bundle('custom_block', $entity->id());
+      custom_block_add_body_field($entity->id());
+    }
+    elseif ($entity->original->id() != $entity->id()) {
+      field_attach_rename_bundle('custom_block', $entity->original->id(), $entity->id());
+    }
+  }
+
+  /**
+   * Overrides \Drupal\Core\Config\Entity\ConfigStorageController::postDelete().
+   */
+  protected function postDelete($entities) {
+    parent::postDelete($entities);
+
+    foreach ($entities as $entity) {
+      field_attach_delete_bundle('custom_block', $entity->id());
+    }
+  }
+
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php
new file mode 100644
index 0000000000000000000000000000000000000000..c73d76d3c7fc305366aa1d226d52c653347db5d5
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php
@@ -0,0 +1,207 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\Plugin\Core\Entity\CustomBlock.
+ */
+
+namespace Drupal\custom_block\Plugin\Core\Entity;
+
+use Drupal\Core\Entity\ContentEntityInterface;
+use Drupal\Core\Entity\EntityNG;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * Defines the custom block entity class.
+ *
+ * @Plugin(
+ *   id = "custom_block",
+ *   label = @Translation("Custom Block"),
+ *   bundle_label = @Translation("Custom Block type"),
+ *   module = "custom_block",
+ *   controller_class = "Drupal\custom_block\CustomBlockStorageController",
+ *   access_controller_class = "Drupal\custom_block\CustomBlockAccessController",
+ *   render_controller_class = "Drupal\custom_block\CustomBlockRenderController",
+ *   form_controller_class = {
+ *     "default" = "Drupal\custom_block\CustomBlockFormController"
+ *   },
+ *   translation_controller_class = "Drupal\custom_block\CustomBlockTranslationController",
+ *   base_table = "custom_block",
+ *   revision_table = "custom_block_revision",
+ *   menu_base_path = "block/%custom_block",
+ *   fieldable = TRUE,
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "revision" = "revision_id",
+ *     "bundle" = "type",
+ *     "label" = "info",
+ *     "uuid" = "uuid"
+ *   },
+ *   bundle_keys = {
+ *     "bundle" = "type"
+ *   }
+ * )
+ */
+class CustomBlock extends EntityNG implements ContentEntityInterface {
+
+  /**
+   * The block ID.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $id;
+
+  /**
+   * The block revision ID.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $revision_id;
+
+  /**
+   * Indicates whether this is the default block revision.
+   *
+   * The default revision of a block is the one loaded when no specific revision
+   * has been specified. Only default revisions are saved to the block_custom
+   * table.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $isDefaultRevision = TRUE;
+
+  /**
+   * The block UUID.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $uuid;
+
+  /**
+   * The custom block type (bundle).
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $type;
+
+  /**
+   * The block language code.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $langcode;
+
+  /**
+   * The block description.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $info;
+
+  /**
+   * The block revision log message.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $log;
+
+  /**
+   * The theme the block is being created in.
+   *
+   * When creating a new custom block from the block library, the user is
+   * redirected to the configure form for that block in the given theme. The
+   * theme is stored against the block when the custom block add form is shown.
+   *
+   * @var string
+   */
+  protected $theme;
+
+  /**
+   * Implements Drupal\Core\Entity\EntityInterface::id().
+   */
+  public function id() {
+    return $this->id->value;
+  }
+
+  /**
+   * Implements Drupal\Core\Entity\EntityInterface::bundle().
+   */
+  public function bundle() {
+    return $this->type->value;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\Entity::createDuplicate().
+   */
+  public function createDuplicate() {
+    $duplicate = parent::createDuplicate();
+    $duplicate->revision_id->value = NULL;
+    $duplicate->id->value = NULL;
+    return $duplicate;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\Entity::getRevisionId().
+   */
+  public function getRevisionId() {
+    return $this->revision_id->value;
+  }
+
+  /**
+   * Sets the theme value.
+   *
+   * When creating a new custom block from the block library, the user is
+   * redirected to the configure form for that block in the given theme. The
+   * theme is stored against the block when the custom block add form is shown.
+   *
+   * @param string $theme
+   *   The theme name.
+   */
+  public function setTheme($theme) {
+    $this->theme = $theme;
+  }
+
+  /**
+   * Gets the theme value.
+   *
+   * When creating a new custom block from the block library, the user is
+   * redirected to the configure form for that block in the given theme. The
+   * theme is stored against the block when the custom block add form is shown.
+   *
+   * @return string
+   *   The theme name.
+   */
+  public function getTheme() {
+    return $this->theme;
+  }
+
+  /**
+   * Initialize the object. Invoked upon construction and wake up.
+   */
+  protected function init() {
+    parent::init();
+    // We unset all defined properties except theme, so magic getters apply.
+    // $this->theme is a special use-case that is only used in the lifecycle of
+    // adding a new block using the block library.
+    unset($this->id);
+    unset($this->info);
+    unset($this->revision_id);
+    unset($this->log);
+    unset($this->uuid);
+    unset($this->type);
+    unset($this->new);
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\Entity::getRevisionId().
+   */
+  public function uri() {
+    return array(
+      'path' => 'block/' . $this->id(),
+      'options' => array(
+        'entity_type' => $this->entityType,
+        'entity' => $this,
+      )
+    );
+  }
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlockType.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlockType.php
new file mode 100644
index 0000000000000000000000000000000000000000..0ad0c32f0ac81086707c0f15d749eea52d186311
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlockType.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\Plugin\Core\Entity\CustomBlockType.
+ */
+
+namespace Drupal\custom_block\Plugin\Core\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * Defines the custom block type entity.
+ *
+ * @Plugin(
+ *   id = "custom_block_type",
+ *   label = @Translation("Custom block type"),
+ *   module = "custom_block",
+ *   controller_class = "Drupal\custom_block\CustomBlockTypeStorageController",
+ *   list_controller_class = "Drupal\custom_block\CustomBlockTypeListController",
+ *   form_controller_class = {
+ *     "default" = "Drupal\custom_block\CustomBlockTypeFormController"
+ *   },
+ *   config_prefix = "custom_block.type",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "label",
+ *     "uuid" = "uuid"
+ *   }
+ * )
+ */
+class CustomBlockType extends ConfigEntityBase {
+
+  /**
+   * The custom block type ID.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The custom block type UUID.
+   *
+   * @var string
+   */
+  public $uuid;
+
+  /**
+   * The custom block type label.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The default revision setting for custom blocks of this type.
+   *
+   * @var bool
+   */
+  public $revision;
+
+  /**
+   * The description of the block type.
+   *
+   * @var string
+   */
+  public $description;
+
+  /**
+   * Overrides \Drupal\Core\Entity\Entity::getRevisionId().
+   */
+  public function uri() {
+    return array(
+      'path' => 'admin/structure/custom-blocks/manage/' . $this->id(),
+      'options' => array(
+        'entity_type' => $this->entityType,
+        'entity' => $this,
+      )
+    );
+  }
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Derivative/CustomBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Derivative/CustomBlock.php
index 9ff46119d4196cd933bdece6c16d1470f6597909..a94fbe356fdcccd7c9d6ddb3ba911c22cbdc2462 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Derivative/CustomBlock.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Derivative/CustomBlock.php
@@ -40,17 +40,14 @@ public function getDerivativeDefinition($derivative_id, array $base_plugin_defin
    * Retrieves custom block definitions from storage.
    */
   public function getDerivativeDefinitions(array $base_plugin_definition) {
-    $results = db_query('SELECT * FROM {block_custom}');
-    foreach ($results as $result) {
-      $this->derivatives[$result->bid] = $base_plugin_definition;
-      $this->derivatives[$result->bid]['settings'] = array(
-        'info' => $result->info,
-        'body' => $result->body,
-        'format' => $result->format,
+    $custom_blocks = entity_load_multiple('custom_block');
+    foreach ($custom_blocks as $custom_block) {
+      $this->derivatives[$custom_block->uuid->value] = $base_plugin_definition;
+      $this->derivatives[$custom_block->uuid->value]['settings'] = array(
+        'info' => $custom_block->info->value,
       ) + $base_plugin_definition['settings'];
-      $this->derivatives[$result->bid]['subject'] = $result->info;
+      $this->derivatives[$custom_block->uuid->value]['subject'] = $custom_block->info->value;
     }
-    $this->derivatives['custom_block'] = $base_plugin_definition;
     return $this->derivatives;
   }
 }
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
deleted file mode 100644
index 84cd7571363c89c490b60d56d1513db282cb5406..0000000000000000000000000000000000000000
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlock.php
+++ /dev/null
@@ -1,119 +0,0 @@
-<?php
-
-/**
- * Contains \Drupal\custom_block\Plugin\block\block\CustomBlock.
- */
-
-namespace Drupal\custom_block\Plugin\block\block;
-
-use Drupal\block\BlockBase;
-use Drupal\Core\Annotation\Plugin;
-use Drupal\Core\Annotation\Translation;
-
-/**
- * Defines a generic custom block type.
- *
- * @Plugin(
- *  id = "custom_block",
- *  subject = @Translation("Custom Block"),
- *  module = "custom_block",
- *  derivative = "Drupal\custom_block\Plugin\Derivative\CustomBlock",
- *  settings = {
- *    "status" = TRUE,
- *    "info" = "",
- *    "body" = "",
- *    "format" = NULL
- *   }
- * )
- */
-class CustomBlock extends BlockBase {
-
-  /**
-   * Overrides \Drupal\block\BlockBase::getConfig().
-   */
-  public function getConfig() {
-    $definition = $this->getDefinition();
-    $this->configuration = parent::getConfig();
-    $this->configuration['status'] = $definition['settings']['status'];
-    $this->configuration['info'] = $definition['settings']['info'];
-    $this->configuration['body'] = $definition['settings']['body'];
-    $this->configuration['format'] = $definition['settings']['format'];
-    return $this->configuration;
-  }
-
-  /**
-   * Overrides \Drupal\block\BlockBase::blockForm().
-   *
-   * Adds body and description fields to the block configuration form.
-   */
-  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['info'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Description'),
-      '#required' => TRUE,
-      '#default_value' => $this->configuration['info'],
-      '#description' => t('A brief description of your block. Used on the <a href="@overview">Blocks administration page</a>. <strong>Changing this field will change the description for all copies of this block.</strong>', array('@overview' => url('admin/structure/block'))),
-    );
-    // @todo Disable this field when editing an existing block and provide a
-    //   separate interface for administering custom blocks.
-    $form['body'] = array(
-      '#type' => 'text_format',
-      '#title' => t('Body'),
-      '#default_value' => $this->configuration['body'],
-      '#format' => isset($this->configuration['format']) ? $this->configuration['format'] : filter_default_format(),
-      '#description' => t('The content of the block as shown to the user. <strong>Changing this field will change the block body everywhere it is used.</strong>'),
-      '#rows' => 15,
-      '#required' => TRUE,
-    );
-    $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) {
-    list(, $bid) = explode(':', $form_state['entity']->get('plugin'));
-    $custom_block_exists = (bool) db_query_range('SELECT 1 FROM {block_custom} WHERE bid <> :bid AND info = :info', 0, 1, array(
-      ':bid' => $bid,
-      ':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) {
-    list(, $bid) = explode(':', $this->getPluginId());
-    $block = array(
-      'info' => $form_state['values']['info'],
-      'body' => $form_state['values']['body']['value'],
-      'format' => $form_state['values']['body']['format'],
-      'bid' => is_numeric($bid) ? $bid : NULL,
-    );
-    drupal_write_record('block_custom', $block, !is_null($block['bid']) ? array('bid') : array());
-    $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();
-    }
-  }
-
-  /**
-   * Implements \Drupal\block\BlockBase::build().
-   */
-  public function build() {
-    // Populate the block with the user-defined block body.
-    return array(
-      '#theme' => 'custom_block_block',
-      '#body' => $this->configuration['body'],
-      '#format' => $this->configuration['format'],
-    );
-  }
-
-}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlockBlock.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlockBlock.php
new file mode 100644
index 0000000000000000000000000000000000000000..ce89a588ec481961496d1bc015eb6266c741d9f9
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlockBlock.php
@@ -0,0 +1,94 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\Plugin\block\block\CustomBlockBlock.
+ */
+
+namespace Drupal\custom_block\Plugin\block\block;
+
+use Drupal\block\BlockBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * Defines a generic custom block type.
+ *
+ * @Plugin(
+ *  id = "custom_block",
+ *  subject = @Translation("Custom Block"),
+ *  module = "custom_block",
+ *  derivative = "Drupal\custom_block\Plugin\Derivative\CustomBlock",
+ *  settings = {
+ *    "status" = TRUE,
+ *    "info" = "",
+ *    "view_mode" = "full"
+ *   }
+ * )
+ */
+class CustomBlockBlock extends BlockBase {
+
+  /**
+   * Overrides \Drupal\block\BlockBase::getConfig().
+   */
+  public function getConfig() {
+    $definition = $this->getDefinition();
+    $this->configuration = parent::getConfig();
+    $this->configuration['status'] = $definition['settings']['status'];
+    $this->configuration['info'] = $definition['settings']['info'];
+    $this->configuration['view_mode'] = $definition['settings']['view_mode'];
+    return $this->configuration;
+  }
+
+  /**
+   * Overrides \Drupal\block\BlockBase::blockForm().
+   *
+   * Adds body and description fields to the block configuration form.
+   */
+  public function blockForm($form, &$form_state) {
+    $options = array();
+    $view_modes = entity_get_view_modes('custom_block');
+    foreach ($view_modes as $view_mode => $detail) {
+      $options[$view_mode] = $detail['label'];
+    }
+    $form['custom_block']['view_mode'] = array(
+      '#type' => 'select',
+      '#options' => $options,
+      '#title' => t('View mode'),
+      '#description' => t('Output the block in this view mode.'),
+      '#default_value' => $this->configuration['view_mode']
+    );
+    $form['title']['#description'] = t('The title of the block as shown to the user.');
+    return $form;
+  }
+
+  /**
+   * Overrides \Drupal\block\BlockBase::blockSubmit().
+   */
+  public function blockSubmit($form, &$form_state) {
+    // Invalidate the block cache to update custom block-based derivatives.
+    if (module_exists('block')) {
+      drupal_container()->get('plugin.manager.block')->clearCachedDefinitions();
+    }
+  }
+
+  /**
+   * Implements \Drupal\block\BlockBase::build().
+   */
+  public function build() {
+    // @todo Clean up when http://drupal.org/node/1874498 lands.
+    list(, $uuid) = explode(':', $this->getPluginId());
+    if ($block = entity_load_by_uuid('custom_block', $uuid)) {
+      return entity_view($block, $this->configuration['view_mode']);
+    }
+    else {
+      return array(
+        '#markup' => t('Block with uuid %uuid does not exist. <a href="!url">Add custom block</a>.', array(
+          '%uuid' => $uuid,
+          '!url' => url('block/add')
+        )),
+        '#access' => user_access('administer blocks')
+      );
+    }
+  }
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockBuildContentTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockBuildContentTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..cee6ce8d998601b45cc6da022ef96a6271ee2a48
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockBuildContentTest.php
@@ -0,0 +1,41 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\Tests\CustomBlockBuildContentTest.
+ */
+
+namespace Drupal\custom_block\Tests;
+
+/**
+ * Test to ensure that a block's content is always rebuilt.
+ */
+class CustomBlockBuildContentTest extends CustomBlockTestBase {
+
+  /**
+   * Declares test information.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Rebuild content',
+      'description' => 'Test the rebuilding of content for full view modes.',
+      'group' => 'Custom Block',
+    );
+  }
+
+  /**
+   * Ensures that content is rebuilt in calls to custom_block_build_content().
+   */
+  public function testCustomBlockRebuildContent() {
+    $block = $this->createCustomBlock();
+
+    // Set a property in the content array so we can test for its existence later on.
+    $block->content['test_content_property'] = array(
+      '#value' => $this->randomString(),
+    );
+    $content = entity_view_multiple(array($block), 'full');
+
+    // If the property doesn't exist it means the block->content was rebuilt.
+    $this->assertFalse(isset($content['test_content_property']), 'Custom block content was emptied prior to being built.');
+  }
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockCreationTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockCreationTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..ba86c1bac7f104ff405ec57a095d0b491bce9a7d
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockCreationTest.php
@@ -0,0 +1,104 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\Tests\CustomBlockCreationTest.
+ */
+
+namespace Drupal\custom_block\Tests;
+
+use Drupal\Core\Database\Database;
+
+/**
+ * Tests creating and saving a block.
+ */
+class CustomBlockCreationTest extends CustomBlockTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * Enable dummy module that implements hook_block_insert() for exceptions.
+   *
+   * @var array
+   */
+  public static $modules = array('custom_block_test', 'dblog');
+
+  /**
+   * Declares test information.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Custom Block creation',
+      'description' => 'Create a block and test saving it.',
+      'group' => 'Custom Block',
+    );
+  }
+
+  /**
+   * Sets the test up.
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->drupalLogin($this->adminUser);
+  }
+
+  /**
+   * Creates a "Basic page" block and verifies its consistency in the database.
+   */
+  public function testCustomBlockCreation() {
+    // Create a block.
+    $edit = array();
+    $langcode = LANGUAGE_NOT_SPECIFIED;
+    $edit['info'] = $this->randomName(8);
+    $edit["block_body[$langcode][0][value]"] = $this->randomName(16);
+    $this->drupalPost('block/add/basic', $edit, t('Save'));
+
+    // Check that the Basic block has been created.
+    $this->assertRaw(format_string('!block %name has been created.', array(
+      '!block' => 'Basic block',
+      '%name' => $edit["info"]
+    )), 'Basic block created.');
+
+    // Check that the block exists in the database.
+    $blocks = entity_load_multiple_by_properties('custom_block', array('info' => $edit['info']));
+    $block = reset($blocks);
+    $this->assertTrue($block, 'Custom Block found in database.');
+  }
+
+  /**
+   * Verifies that a transaction rolls back the failed creation.
+   */
+  public function testFailedBlockCreation() {
+    // Create a block.
+    try {
+      $this->createCustomBlock('fail_creation');
+      $this->fail('Expected exception has not been thrown.');
+    }
+    catch (\Exception $e) {
+      $this->pass('Expected exception has been thrown.');
+    }
+
+    if (Database::getConnection()->supportsTransactions()) {
+      // Check that the block does not exist in the database.
+      $id = db_select('custom_block', 'b')
+        ->fields('b', array('id'))
+        ->condition('info', 'fail_creation')
+        ->execute()
+        ->fetchField();
+      $this->assertFalse($id, 'Transactions supported, and block not found in database.');
+    }
+    else {
+      // Check that the block exists in the database.
+      $id = db_select('custom_block', 'b')
+        ->fields('b', array('id'))
+        ->condition('info', 'fail_creation')
+        ->execute()
+        ->fetchField();
+      $this->assertTrue($id, 'Transactions not supported, and block found in database.');
+
+      // Check that the failed rollback was logged.
+      $records = db_query("SELECT wid FROM {watchdog} WHERE message LIKE 'Explicit rollback failed%'")->fetchAll();
+      $this->assertTrue(count($records) > 0, 'Transactions not supported, and rollback error logged to watchdog.');
+    }
+  }
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockLoadHooksTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockLoadHooksTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..717d659101ef37391930d08a3a7266efca6ef16d
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockLoadHooksTest.php
@@ -0,0 +1,70 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\Tests\CustomBlockLoadHooksTest.
+ */
+
+namespace Drupal\custom_block\Tests;
+
+/**
+ * Tests for the hooks invoked during custom_block_load().
+ */
+class CustomBlockLoadHooksTest extends CustomBlockTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('custom_block_test');
+
+  /**
+   * Declares test information.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Custom Block load hooks',
+      'description' => 'Test the hooks invoked when a custom block is being loaded.',
+      'group' => 'Custom Block',
+    );
+  }
+
+  /**
+   * Tests that hook_custom_block_load() is invoked correctly.
+   */
+  public function testHookCustomBlockLoad() {
+    $other_bundle = $this->createCustomBlockType('other');
+    // Create some sample articles and pages.
+    $custom_block1 = $this->createCustomBlock();
+    $custom_block2 = $this->createCustomBlock();
+    $custom_block3 = $this->createCustomBlock();
+    $custom_block4 = $this->createCustomBlock(FALSE, $other_bundle->id());
+
+    // Check that when a set of custom blocks that only contains basic blocks is
+    // loaded, the properties added to the custom block by
+    // custom_block_test_load_custom_block() correctly reflect the expected
+    // values.
+    $custom_blocks = entity_load_multiple_by_properties('custom_block', array('type' => 'basic'));
+    $loaded_custom_block = end($custom_blocks);
+    $this->assertEqual($loaded_custom_block->custom_block_test_loaded_ids, array(
+      $custom_block1->id->value,
+      $custom_block2->id->value,
+      $custom_block3->id->value
+    ), 'hook_custom_block_load() received the correct list of custom_block IDs the first time it was called.');
+    $this->assertEqual($loaded_custom_block->custom_block_test_loaded_types, array('basic'), 'hook_custom_block_load() received the correct list of custom block types the first time it was called.');
+
+    // Now, as part of the same page request, load a set of custom_blocks that contain
+    // both basic and other bundle, and make sure the parameters passed to
+    // custom_block_test_custom_block_load() are correctly updated.
+    $custom_blocks = entity_load_multiple('custom_block', entity_query('custom_block')->execute(), TRUE);
+    $loaded_custom_block = end($custom_blocks);
+    $this->assertEqual($loaded_custom_block->custom_block_test_loaded_ids, array(
+      $custom_block1->id->value,
+      $custom_block2->id->value,
+      $custom_block3->id->value,
+      $custom_block4->id->value
+    ), 'hook_custom_block_load() received the correct list of custom_block IDs the second time it was called.');
+    $this->assertEqual($loaded_custom_block->custom_block_test_loaded_types, array('basic', 'other'), 'hook_custom_block_load() received the correct list of custom_block types the second time it was called.');
+  }
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockRevisionsTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockRevisionsTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..b1d1220166cb7389d1abfe1283093906dbfc65d4
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockRevisionsTest.php
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\Tests\CustomBlockRevisionsTest.
+ */
+
+namespace Drupal\custom_block\Tests;
+
+/**
+ * Tests the block revision functionality.
+ */
+class CustomBlockRevisionsTest extends CustomBlockTestBase {
+
+  /**
+   * Stores blocks created during the test.
+   * @var array
+   */
+  protected $blocks;
+
+  /**
+   * Stores log messages used during the test.
+   * @var array
+   */
+  protected $logs;
+
+  /**
+   * Declares test information.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Custom Block revisions',
+      'description' => 'Create a block with revisions.',
+      'group' => 'Custom Block',
+    );
+  }
+
+  /**
+   * Sets the test up.
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    // Create initial block.
+    $block = $this->createCustomBlock('initial');
+
+    $blocks = array();
+    $logs = array();
+
+    // Get original block.
+    $blocks[] = $block->revision_id->value;
+    $logs[] = '';
+
+    // Create three revisions.
+    $revision_count = 3;
+    for ($i = 0; $i < $revision_count; $i++) {
+      $block->setNewRevision(TRUE);
+      $logs[] = $block->log->value = $this->randomName(32);
+      $block->save();
+      $blocks[] = $block->revision_id->value;
+    }
+
+    $this->blocks = $blocks;
+    $this->logs = $logs;
+  }
+
+  /**
+   * Checks block revision related operations.
+   */
+  public function testRevisions() {
+    $blocks = $this->blocks;
+    $logs = $this->logs;
+
+    foreach ($blocks as $delta => $revision_id) {
+      // Confirm the correct revision text appears.
+      $loaded = entity_revision_load('custom_block', $revision_id);
+      // Verify log is the same.
+      $this->assertEqual($loaded->log->value, $logs[$delta], format_string('Correct log message found for revision !revision', array(
+        '!revision' => $loaded->revision_id->value
+      )));
+    }
+
+    // Confirm that this is the default revision.
+    $this->assertTrue($loaded->isDefaultRevision(), 'Third block revision is the default one.');
+
+    // Make a new revision and set it to not be default.
+    // This will create a new revision that is not "front facing".
+    // Save this as a non-default revision.
+    $loaded->setNewRevision();
+    $loaded->isDefaultRevision = FALSE;
+    $loaded->block_body = $this->randomName(8);
+    $loaded->save();
+
+    $this->drupalGet('block/' . $loaded->id->value);
+    $this->assertNoText($loaded->block_body->value, 'Revision body text is not present on default version of block.');
+
+    // Verify that the non-default revision id is greater than the default
+    // revision id.
+    $default_revision = entity_load('custom_block', $loaded->id->value);
+    $this->assertTrue($loaded->revision_id->value > $default_revision->revision_id->value, 'Revision id is greater than default revision id.');
+  }
+
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockSaveTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockSaveTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..1874ff3425c6429abfffa6c8d6d89688cf402a2b
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockSaveTest.php
@@ -0,0 +1,111 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\Tests\CustomBlockSaveTest.
+ */
+
+namespace Drupal\custom_block\Tests;
+
+/**
+ * Tests block save related functionality.
+ */
+class CustomBlockSaveTest extends CustomBlockTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('custom_block_test');
+
+  /**
+   * Declares test information.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Custom Block save',
+      'description' => 'Test $custom_block->save() for saving content.',
+      'group' => 'Custom Block',
+    );
+  }
+
+  /**
+   * Sets the test up.
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->drupalLogin($this->adminUser);
+  }
+
+  /**
+   * Checks whether custom block IDs are saved properly during an import.
+   */
+  public function testImport() {
+    // Custom block ID must be a number that is not in the database.
+    $max_id = db_query('SELECT MAX(id) FROM {custom_block}')->fetchField();
+    $test_id = $max_id + mt_rand(1000, 1000000);
+    $info = $this->randomName(8);
+    $block = array(
+      'info' => $info,
+      'block_body' => array(LANGUAGE_NOT_SPECIFIED => array(array('value' => $this->randomName(32)))),
+      'type' => 'basic',
+      'id' => $test_id
+    );
+    $block = entity_create('custom_block', $block);
+    $block->enforceIsNew(TRUE);
+    $block->save();
+
+    // Verify that block_submit did not wipe the provided id.
+    $this->assertEqual($block->id->value, $test_id, 'Block imported using provide id');
+
+    // Test the import saved.
+    $block_by_id = custom_block_load($test_id);
+    $this->assertTrue($block_by_id, 'Custom block load by block ID.');
+  }
+
+  /**
+   * Tests determing changes in hook_block_presave().
+   *
+   * Verifies the static block load cache is cleared upon save.
+   */
+  public function testDeterminingChanges() {
+    // Initial creation.
+    $block = $this->createCustomBlock('test_changes');
+
+    // Update the block without applying changes.
+    $block->save();
+    $this->assertEqual($block->label(), 'test_changes', 'No changes have been determined.');
+
+    // Apply changes.
+    $block->info->value = 'updated';
+    $block->save();
+
+    // The hook implementations custom_block_test_custom_block_presave() and
+    // custom_block_test_custom_block_update() determine changes and change the
+    // title.
+    $this->assertEqual($block->label(), 'updated_presave_update', 'Changes have been determined.');
+
+    // Test the static block load cache to be cleared.
+    $block = custom_block_load($block->id->value);
+    $this->assertEqual($block->label(), 'updated_presave', 'Static cache has been cleared.');
+  }
+
+  /**
+   * Tests saving a block on block insert.
+   *
+   * This test ensures that a block has been fully saved when
+   * hook_custom_block_insert() is invoked, so that the block can be saved again
+   * in a hook implementation without errors.
+   *
+   * @see block_test_block_insert()
+   */
+  public function testCustomBlockSaveOnInsert() {
+    // custom_block_test_custom_block_insert() tiggers a save on insert if the
+    // title equals 'new'.
+    $block = $this->createCustomBlock('new');
+    $this->assertEqual($block->label(), 'CustomBlock ' . $block->id->value, 'Custom block saved on block insert.');
+  }
+
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTestBase.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTestBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..55d6bb94424b65748e08ed1b04b26e047322615d
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTestBase.php
@@ -0,0 +1,96 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\Tests\CustomBlockTestBase.
+ */
+
+namespace Drupal\custom_block\Tests;
+
+use Drupal\simpletest\WebTestBase;
+
+/**
+ * Sets up page and article content types.
+ */
+abstract class CustomBlockTestBase extends WebTestBase {
+
+  /**
+   * Profile to use.
+   */
+  protected $profile = 'testing';
+
+  /**
+   * Admin user
+   *
+   * @var object
+   */
+  protected $adminUser;
+
+  /**
+   * Permissions to grant admin user.
+   *
+   * @var array
+   */
+  protected $permissions = array(
+    'administer blocks'
+  );
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('block', 'custom_block');
+
+  /**
+   * Sets the test up.
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->adminUser = $this->drupalCreateUser($this->permissions);
+  }
+
+  /**
+   * Creates a custom block.
+   *
+   * @param string $title
+   *   (optional) Title of block. When no value is given uses a random name.
+   *   Defaults to FALSE.
+   * @param string $bundle
+   *   (optional) Bundle name. Defaults to 'basic'.
+   *
+   * @return Drupal\custom_block\Plugin\Core\Entity\CustomBlock
+   *   Created custom block.
+   */
+  protected function createCustomBlock($title = FALSE, $bundle = 'basic') {
+    $title = ($title ? : $this->randomName());
+    if ($custom_block = entity_create('custom_block', array(
+      'info' => $title,
+      'type' => $bundle,
+      'langcode' => 'en'
+    ))) {
+      $custom_block->save();
+    }
+    return $custom_block;
+  }
+
+  /**
+   * Creates a custom block type (bundle).
+   *
+   * @param string $label
+   *   The block type label.
+   *
+   * @return Drupal\custom_block\Plugin\Core\Entity\CustomBlockType
+   *   Created custom block type.
+   */
+  protected function createCustomBlockType($label) {
+    $bundle = entity_create('custom_block_type', array(
+      'id' => $label,
+      'label' => $label,
+      'revision' => FALSE
+    ));
+    $bundle->save();
+    return $bundle;
+  }
+
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTranslationUITest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTranslationUITest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3ccac91361b7f1ae4cf41867b003659fdcbc5f9b
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTranslationUITest.php
@@ -0,0 +1,126 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\Tests\CustomBlockTranslationUITest.
+ */
+
+namespace Drupal\custom_block\Tests;
+
+use Drupal\translation_entity\Tests\EntityTranslationUITest;
+use Drupal\custom_block\Plugin\Core\Entity\CustomBlock;
+
+/**
+ * Tests the Custom Block Translation UI.
+ */
+class CustomBlockTranslationUITest extends EntityTranslationUITest {
+
+  /**
+   * The name of the test block.
+   */
+  protected $name;
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array(
+    'language',
+    'translation_entity',
+    'block',
+    'field_ui',
+    'custom_block'
+  );
+
+  /**
+   * Declares test information.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Custom Block translation UI',
+      'description' => 'Tests the node translation UI.',
+      'group' => 'Custom Block',
+    );
+  }
+
+  /**
+   * Overrides \Drupal\simpletest\WebTestBase::setUp().
+   */
+  public function setUp() {
+    $this->entityType = 'custom_block';
+    $this->bundle = 'basic';
+    $this->name = drupal_strtolower($this->randomName());
+    $this->testLanguageSelector = FALSE;
+    parent::setUp();
+  }
+
+  /**
+   * Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getTranslatorPermission().
+   */
+  public function getTranslatorPermissions() {
+    return array_merge(parent::getTranslatorPermissions(), array(
+      'translate any entity',
+      'access administration pages',
+      'administer blocks',
+      'administer custom_block fields'
+    ));
+  }
+
+  /**
+   * Creates a custom block.
+   *
+   * @param string $title
+   *   (optional) Title of block. When no value is given uses a random name.
+   *   Defaults to FALSE.
+   * @param string $bundle
+   *   (optional) Bundle name. When no value is given, defaults to
+   *   $this->bundle. Defaults to FALSE.
+   *
+   * @return Drupal\custom_block\Plugin\Core\Entity\CustomBlock
+   *   Created custom block.
+   */
+  protected function createCustomBlock($title = FALSE, $bundle = FALSE) {
+    $title = ($title ? : $this->randomName());
+    $bundle = ($bundle ? : $this->bundle);
+    $custom_block = entity_create('custom_block', array(
+      'info' => $title,
+      'type' => $bundle,
+      'langcode' => 'en'
+    ));
+    $custom_block->save();
+    return $custom_block;
+  }
+
+  /**
+   * Overrides \Drupal\translation_entity\Tests\EntityTranslationUITest::getNewEntityValues().
+   */
+  protected function getNewEntityValues($langcode) {
+    return array('info' => $this->name) + parent::getNewEntityValues($langcode);
+  }
+
+  /**
+   * Test that no metadata is stored for a disabled bundle.
+   */
+  public function testDisabledBundle() {
+    // Create a bundle that does not have translation enabled.
+    $disabled_bundle = $this->randomName();
+    $bundle = entity_create('custom_block_type', array(
+      'id' => $disabled_bundle,
+      'label' => $disabled_bundle,
+      'revision' => FALSE
+    ));
+    $bundle->save();
+
+    // Create a node for each bundle.
+    $enabled_custom_block = $this->createCustomBlock();
+    $disabled_custom_block = $this->createCustomBlock(FALSE, $bundle->id());
+
+    // Make sure that only a single row was inserted into the
+    // {translation_entity} table.
+    $rows = db_query('SELECT * FROM {translation_entity}')->fetchAll();
+    $this->assertEqual(1, count($rows));
+    $this->assertEqual($enabled_custom_block->id(), reset($rows)->entity_id);
+  }
+
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTypeTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTypeTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..d94e47638cd96b5d75be8705d05f04cac49eddac
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/CustomBlockTypeTest.php
@@ -0,0 +1,136 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\Tests\CustomBlockTypeTest.
+ */
+
+namespace Drupal\custom_block\Tests;
+
+/**
+ * Tests related to custom block types.
+ */
+class CustomBlockTypeTest extends CustomBlockTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('field_ui');
+
+  /**
+   * Permissions to grant admin user.
+   *
+   * @var array
+   */
+  protected $permissions = array(
+    'administer blocks',
+    'administer custom_block fields'
+  );
+
+  /**
+   * Declares test information.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'CustomBlock types',
+      'description' => 'Ensures that custom block type functions work correctly.',
+      'group' => 'Custom Block',
+    );
+  }
+
+  /**
+   * Tests creating a block type programmatically and via a form.
+   */
+  public function testCustomBlockTypeCreation() {
+    // Create a block type programmaticaly.
+    $type = $this->createCustomBlockType('other');
+
+    $block_type = entity_load('custom_block_type', 'other');
+    $this->assertTrue($block_type, 'The new block type has been created.');
+
+    // Login a test user.
+    $this->drupalLogin($this->adminUser);
+
+    $this->drupalGet('block/add/' . $type->id());
+    $this->assertResponse(200, 'The new block type can be accessed at bloack/add.');
+
+    // Create a block type via the user interface.
+    $edit = array(
+      'id' => 'foo',
+      'label' => 'title for foo',
+    );
+    $this->drupalPost('admin/structure/custom-blocks/add', $edit, t('Save'));
+    $block_type = entity_load('custom_block_type', 'foo');
+    $this->assertTrue($block_type, 'The new block type has been created.');
+  }
+
+  /**
+   * Tests editing a block type using the UI.
+   */
+  public function testCustomBlockTypeEditing() {
+    $this->drupalLogin($this->adminUser);
+    // We need two block types to prevent /block/add redirecting.
+    $this->createCustomBlockType('other');
+
+    $instance = field_info_instance('custom_block', 'block_body', 'basic');
+    $this->assertEqual($instance['label'], 'Block body', 'Body field was found.');
+
+    // Verify that title and body fields are displayed.
+    $this->drupalGet('block/add/basic');
+    $this->assertRaw('Block description', 'Block info field was found.');
+    $this->assertRaw('Block body', 'Body field was found.');
+
+    // Change the block type name.
+    $edit = array(
+      'label' => 'Bar',
+    );
+    $this->drupalPost('admin/structure/custom-blocks/manage/basic', $edit, t('Save'));
+    field_info_cache_clear();
+
+    $this->drupalGet('block/add');
+    $this->assertRaw('Bar', 'New name was displayed.');
+    $this->clickLink('Bar');
+    $this->assertEqual(url('block/add/basic', array('absolute' => TRUE)), $this->getUrl(), 'Original machine name was used in URL.');
+
+    // Remove the body field.
+    $this->drupalPost('admin/structure/custom-blocks/manage/basic/fields/block_body/delete', array(), t('Delete'));
+    // Resave the settings for this type.
+    $this->drupalPost('admin/structure/custom-blocks/manage/basic', array(), t('Save'));
+    // Check that the body field doesn't exist.
+    $this->drupalGet('block/add/basic');
+    $this->assertNoRaw('Block body', 'Body field was not found.');
+  }
+
+  /**
+   * Tests deleting a block type that still has content.
+   */
+  public function testCustomBlockTypeDeletion() {
+    // Create a block type programmatically.
+    $type = $this->createCustomBlockType('foo');
+
+    $this->drupalLogin($this->adminUser);
+
+    // Add a new block of this type.
+    $block = $this->createCustomBlock(FALSE, 'foo');
+    // Attempt to delete the block type, which should not be allowed.
+    $this->drupalGet('admin/structure/custom-blocks/manage/' . $type->id() . '/delete');
+    $this->assertRaw(
+      t('%label is used by 1 custom block on your site. You can not remove this block type until you have removed all of the %label blocks.', array('%label' => $type->label())),
+      'The block type will not be deleted until all blocks of that type are removed.'
+    );
+    $this->assertNoText(t('This action cannot be undone.'), 'The node type deletion confirmation form is not available.');
+
+    // Delete the block.
+    $block->delete();
+    // Attempt to delete the block type, which should now be allowed.
+    $this->drupalGet('admin/structure/custom-blocks/manage/' . $type->id() . '/delete');
+    $this->assertRaw(
+      t('Are you sure you want to delete %type?', array('%type' => $type->id())),
+      'The block type is available for deletion.'
+    );
+    $this->assertText(t('This action cannot be undone.'), 'The custom block type deletion confirmation form is available.');
+  }
+
+}
diff --git a/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/PageEditTest.php b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/PageEditTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..3ba7b0b7a376fd9541c35ad0dfca6cb80cecef5b
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Tests/PageEditTest.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\custom_block\Tests\PageEditTest.
+ */
+
+namespace Drupal\custom_block\Tests;
+
+/**
+ * Tests the block edit functionality.
+ */
+class PageEditTest extends CustomBlockTestBase {
+
+  /**
+   * Declares test information.
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Custom Block edit',
+      'description' => 'Create a block and test block edit functionality.',
+      'group' => 'Custom Block',
+    );
+  }
+
+  /**
+   * Checks block edit functionality.
+   */
+  public function testPageEdit() {
+    $this->drupalLogin($this->adminUser);
+
+    $langcode = LANGUAGE_NOT_SPECIFIED;
+    $title_key = 'info';
+    $body_key = "block_body[$langcode][0][value]";
+    // Create block to edit.
+    $edit = array();
+    $edit['info'] = drupal_strtolower($this->randomName(8));
+    $edit[$body_key] = $this->randomName(16);
+    $this->drupalPost('block/add/basic', $edit, t('Save'));
+
+    // Check that the block exists in the database.
+    $blocks = entity_query('custom_block')->condition('info', $edit['info'])->execute();
+    $block = entity_load('custom_block', reset($blocks));
+    $this->assertTrue($block, 'Custom block found in database.');
+
+    // Load the edit page.
+    $this->drupalGet('block/' . $block->id() . '/edit');
+    $this->assertFieldByName($title_key, $edit[$title_key], 'Title field displayed.');
+    $this->assertFieldByName($body_key, $edit[$body_key], 'Body field displayed.');
+
+    // Edit the content of the block.
+    $edit = array();
+    $edit[$title_key] = $this->randomName(8);
+    $edit[$body_key] = $this->randomName(16);
+    // Stay on the current page, without reloading.
+    $this->drupalPost(NULL, $edit, t('Save'));
+
+    // Edit the same block, creating a new revision.
+    $this->drupalGet("block/" . $block->id() . "/edit");
+    $edit = array();
+    $edit['info'] = $this->randomName(8);
+    $edit[$body_key] = $this->randomName(16);
+    $edit['revision'] = TRUE;
+    $this->drupalPost(NULL, $edit, t('Save'));
+
+    // Ensure that the block revision has been created.
+    $revised_block = entity_load('custom_block', $block->id->value, TRUE);
+    $this->assertNotIdentical($block->revision_id->value, $revised_block->revision_id->value, 'A new revision has been created.');
+  }
+
+}
diff --git a/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.info b/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.info
new file mode 100644
index 0000000000000000000000000000000000000000..999389225165103c00c2479de952d225b2463f6a
--- /dev/null
+++ b/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.info
@@ -0,0 +1,5 @@
+name = "Custom Block module tests"
+description = "Support module for custom block related testing."
+package = Testing
+core = 8.x
+hidden = TRUE
diff --git a/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.module b/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.module
new file mode 100644
index 0000000000000000000000000000000000000000..76bccdc4db8043709761c3e15efcead72b3494db
--- /dev/null
+++ b/core/modules/block/custom_block/tests/modules/custom_block_test/custom_block_test.module
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @file
+ * A dummy module for testing custom block related hooks.
+ *
+ * This is a dummy module that implements custom block related hooks to test API
+ * interaction with the custom_block module.
+ */
+
+use Drupal\custom_block\Plugin\Core\Entity\CustomBlock;
+
+/**
+ * Implements hook_custom_block_load().
+ */
+function custom_block_test_custom_block_load($custom_blocks, $types) {
+  // Add properties to each loaded custom_block which record the parameters that
+  // were passed in to this function, so the tests can check that (a) this hook
+  // was called, and (b) the parameters were what we expected them to be.
+  $ids = array_keys($custom_blocks);
+  ksort($ids);
+  sort($types);
+  foreach ($custom_blocks as $custom_block) {
+    $custom_block->custom_block_test_loaded_ids = $ids;
+    $custom_block->custom_block_test_loaded_types = $types;
+  }
+}
+
+/**
+ * Implements hook_custom_block_view().
+ */
+function custom_block_test_custom_block_view(CustomBlock $custom_block, $view_mode) {
+  // Add extra content.
+  $custom_block->content['extra_content'] = array(
+    '#markup' => '<blink>Yowser</blink>',
+  );
+}
+
+/**
+ * Implements hook_custom_block_presave().
+ */
+function custom_block_test_custom_block_presave(CustomBlock $custom_block) {
+  if ($custom_block->info->value == 'testing_custom_block_presave') {
+    $custom_block->info->value .= '_presave';
+  }
+  // Determine changes.
+  if (!empty($custom_block->original) && $custom_block->original->info->value == 'test_changes') {
+    if ($custom_block->original->info->value != $custom_block->info->value) {
+      $custom_block->info->value .= '_presave';
+    }
+  }
+}
+
+/**
+ * Implements hook_custom_block_update().
+ */
+function custom_block_test_custom_block_update(CustomBlock $custom_block) {
+  // Determine changes on update.
+  if (!empty($custom_block->original) && $custom_block->original->info->value == 'test_changes') {
+    if ($custom_block->original->info->value != $custom_block->info->value) {
+      $custom_block->info->value .= '_update';
+    }
+  }
+}
+
+/**
+ * Implements hook_custom_block_insert().
+ *
+ * This tests saving a custom_block on custom_block insert.
+ *
+ * @see \Drupal\custom_block\Tests\CustomBlockSaveTest::testCustomBlockSaveOnInsert()
+ */
+function custom_block_test_custom_block_insert(CustomBlock $custom_block) {
+  // Set the custom_block title to the custom_block ID and save.
+  if ($custom_block->info->value == 'new') {
+    $custom_block->info->value = 'CustomBlock '. $custom_block->id->value;
+    $custom_block->save();
+  }
+  if ($custom_block->info->value == 'fail_creation') {
+    throw new Exception('Test exception for rollback.');
+  }
+}
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 6a5bb661a504992ad929ba82ff9ef5f5e9d4a8f6..740b34964144e4f7fb936e4b21cca1f0db3bcad0 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
@@ -73,7 +73,7 @@ public function form($form, &$form_state, $facet = NULL) {
     $rows = array();
     foreach ($plugins as $plugin_id => $display_plugin_definition) {
       if (empty($facet) || $this->facetCompare($facet, $display_plugin_definition)) {
-        $rows[] = $this->row($plugin_id, $display_plugin_definition);
+        $rows[$plugin_id] = $this->row($plugin_id, $display_plugin_definition);
       }
       foreach ($plugin_definition['facets'] as $key => $title) {
         $facets[$key][$display_plugin_definition[$key]] = $this->facetLink($key, $plugin_id, $display_plugin_definition);
@@ -95,7 +95,7 @@ public function form($form, &$form_state, $facet = NULL) {
       }
     }
     // Sort rows alphabetically.
-    sort($rows);
+    asort($rows);
     $form['left']['plugin_library'] = array(
       '#theme' => 'table',
       '#header' => $this->tableHeader(),
diff --git a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php
index ee51d4352e8c5cab045d236aed689e2f5f397b83..27dbfa103bd030e91176e916496e6a0d0b4dce1f 100644
--- a/core/modules/block/lib/Drupal/block/Tests/BlockTest.php
+++ b/core/modules/block/lib/Drupal/block/Tests/BlockTest.php
@@ -91,27 +91,31 @@ public function testCustomBlock() {
       }
     }
 
-    // Add a new custom block by filling out the input form on the admin/structure/block/add page.
+    // Add a new custom block by filling out the input form on block/add/basic.
     $info = strtolower($this->randomName(8));
+    $langcode = LANGUAGE_NOT_SPECIFIED;
+    $values = array(
+      'info' => $info,
+      "block_body[$langcode][0][value]" => $this->randomName(8)
+    );
+    $this->drupalPost('block/add/basic', $values, t('Save'));
     $custom_block['machine_name'] = $info;
-    $custom_block['info'] = $info;
     $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'));
+    $this->drupalPost(NULL, $custom_block, t('Save block'));
     $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.');
+    $this->assertText(t('The block configuration has been saved.'), 'Custom block instance successfully created.');
 
-    // 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.
+    // Check that block_block_view() returns the correct content.
     $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.');
+    $output = render($data);
+
+    $this->drupalSetcontent($output);
+    $elements = $this->xpath('//div[@class=:class]', array(':class' => 'field-item even'));
+
+    $this->assertEqual($values["block_body[$langcode][0][value]"], $elements[0], 'BlockInterface::build() provides correct block content.');
 
     // Check whether the block can be moved to all available regions.
     $custom_block['module'] = 'block';
@@ -144,35 +148,42 @@ public function testCustomBlock() {
   public function testCustomBlockFormat() {
     $default_theme = variable_get('theme_default', 'stark');
 
-    // Add a new custom block by filling out the input form on the admin/structure/block/add page.
+    // Add a new custom block by filling out the input form on block/add/basic.
     $info = strtolower($this->randomName(8));
+    $langcode = LANGUAGE_NOT_SPECIFIED;
+    $values = array(
+      'info' => $info,
+      "block_body[$langcode][0][value]" => '<h1>Full HTML</h1>',
+      "block_body[$langcode][0][format]" => 'full_html',
+    );
+    $this->drupalPost('block/add/basic', $values, t('Save'));
+    // Load the block up from the database.
+    $blocks = entity_load_multiple('custom_block');
+    $loaded_block = end($blocks);
     $custom_block['machine_name'] = $info;
-    $custom_block['info'] = $info;
     $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;
     $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'));
+    $this->drupalPost(NULL, $custom_block, t('Save block'));
 
     // Set the created custom block to a specific region.
     $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.
+    // Confirm that the custom block is being displayed using configured text
+    // format.
     $this->drupalGet('');
     $this->assertRaw('<h1>Full HTML</h1>', 'Custom block successfully being displayed using Full HTML.');
 
-    // Confirm that a user without access to Full HTML can not see the body field,
-    // but can still submit the form without errors.
+    // 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'));
     $this->drupalLogin($block_admin);
-    $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("admin/structure/block/manage/$default_theme.$info/configure", array(), t('Save block'));
+    $this->drupalGet("block/" . $loaded_block->id() . "/edit");
+    $this->assertFieldByXPath("//textarea[@name='block_body[und][0][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->assertNoText(t('Ensure that each block description is unique.'));
 
-    // Confirm that the custom block is still being displayed using configured text format.
+    // Confirm that the custom block is still being displayed using configured
+    // text format.
     $this->drupalGet('');
     $this->assertRaw('<h1>Full HTML</h1>', 'Custom block successfully being displayed using Full HTML.');
   }
diff --git a/core/modules/translation_entity/translation_entity.module b/core/modules/translation_entity/translation_entity.module
index e005b94d747d2a6923fa82c6c2141bb111141c57..af2ada855849abbf2d9fdc91525f8dea0243e659 100644
--- a/core/modules/translation_entity/translation_entity.module
+++ b/core/modules/translation_entity/translation_entity.module
@@ -224,7 +224,8 @@ function translation_entity_menu_alter(array &$items) {
       // for this entity type. In some cases the actual base path might not have
       // a menu loader associated, hence we need to check also for the plain "%"
       // variant. See for instance comment_menu().
-      if (!isset($items[$path]) && !isset($items[_translation_entity_menu_strip_loaders($path)])) {
+      if (!isset($items[$path]) && !isset($items["$path/edit"])
+          && !isset($items[_translation_entity_menu_strip_loaders($path)])) {
         unset(
           $items["$path/translations"],
           $items["$path/translations/add/%language/%language"],
diff --git a/core/themes/seven/template.php b/core/themes/seven/template.php
index 4aba1de4ffe3ffcedc7fce8ec28cd6cc46d01626..147e557e7f58c66f796189597db0c4872ce3f93b 100644
--- a/core/themes/seven/template.php
+++ b/core/themes/seven/template.php
@@ -62,6 +62,29 @@ function seven_node_add_list($variables) {
   return $output;
 }
 
+/**
+ * Overrides theme_custom_block_add_list().
+ *
+ * Displays the list of available custom block types for creation.
+ */
+function seven_custom_block_add_list($variables) {
+  $content = $variables['content'];
+  $output = '';
+  if ($content) {
+    $output = '<ul class="admin-list">';
+    foreach ($content as $type) {
+      $output .= '<li class="clearfix">';
+      $content = '<span class="label">' . check_plain($type->label()) . '</span>';
+      $content .= '<div class="description">' . filter_xss_admin($type->description) . '</div>';
+      $options['html'] = TRUE;
+      $output .= l($content, 'block/add/' . $type->id(), $options);
+      $output .= '</li>';
+    }
+    $output .= '</ul>';
+  }
+  return $output;
+}
+
 /**
  * Overrides theme_admin_block_content().
  *