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 0000000..e2bb99e
--- /dev/null
+++ b/core/modules/block/custom_block/config/custom_block.type.basic.yml
@@ -0,0 +1,3 @@
+id: basic
+label: Basic block
+revision: '0'
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 0000000..be1e32e
--- /dev/null
+++ b/core/modules/block/custom_block/custom_block.admin.inc
@@ -0,0 +1,91 @@
+<?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() {
+  drupal_set_title(t('Custom block types'));
+  return entity_list_controller('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() {
+  drupal_set_title(t('Add custom block type'));
+  $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) {
+  drupal_set_title(t('Edit %label custom block type', array('%label' => $block_type->label())), PASS_THROUGH);
+  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(),
+  );
+
+  return confirm_form(
+    $form,
+    t('Are you sure you want to delete %label?', array('%label' => $block_type->label())),
+    '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.install b/core/modules/block/custom_block/custom_block.install
index 4a0e4b0..6345b7f 100644
--- a/core/modules/block/custom_block/custom_block.install
+++ b/core/modules/block/custom_block/custom_block.install
@@ -19,12 +19,11 @@ function custom_block_schema() {
         'not null' => TRUE,
         'description' => "The block's {block}.bid.",
       ),
-      '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,83 @@ function custom_block_schema() {
         'default' => '',
         'description' => 'Block description.',
       ),
-      'format' => array(
+      'machine_name' => array(
         'type' => 'varchar',
-        'length' => 255,
+        'length' => 128,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Machine name of block.',
+      ),
+      // Defaults to NULL in order to avoid a brief period of potential
+      // deadlocks on the index.
+      'vid' => array(
+        'description' => 'The current {block_custom_revision}.vid 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' => '',
       ),
     ),
+    'indexes' => array(
+      'block_custom_type'   => array(array('type', 4)),
+    ),
     'unique keys' => array(
+      'vid' => array('vid'),
+      'uuid' => array('uuid'),
       'info' => array('info'),
     ),
+    'foreign keys' => array(
+      'block_custom_revision' => array(
+        'table' => 'block_custom_revision',
+        'columns' => array('vid' => 'vid'),
+      ),
+    ),
     'primary key' => array('bid'),
   );
+
+  $schema['block_custom_revision'] = array(
+    'description' => 'Stores contents of custom-made blocks.',
+    'fields' => array(
+      'bid' => array(
+        'type' => 'int',
+        'unsigned' => TRUE,
+        'not null' => TRUE,
+        'default' => 0,
+        'description' => "The block's {block}.bid.",
+      ),
+      // Defaults to NULL in order to avoid a brief period of potential
+      // deadlocks on the index.
+      'vid' => 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',
+      ),
+    ),
+    'unique keys' => array(
+      'vid' => array('vid'),
+    ),
+    'primary key' => array('vid'),
+  );
   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 0000000..fb0905a
--- /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 d3e1588..148ab06 100644
--- a/core/modules/block/custom_block/custom_block.module
+++ b/core/modules/block/custom_block/custom_block.module
@@ -5,6 +5,9 @@
  * 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;
+
 /**
  * Implements hook_menu().
  */
@@ -19,19 +22,121 @@ function custom_block_menu() {
       $items['admin/structure/block/list/' . $plugin_id . '/add/custom_blocks'] = array(
         'title' => 'Add custom block',
         'description' => 'Create a block with custom content and settings.',
-        'page callback' => 'drupal_get_form',
-        'page arguments' => array('block_admin_configure', 'custom_block:custom_block', $theme),
-        'access callback' => TRUE,
+        'page callback' => 'drupal_goto',
+        'page arguments' => array(
+          'block/add',
+          array('query' => array('theme' => $theme))
+        ),
+        'access arguments' => array('administer blocks'),
         'type' => MENU_LOCAL_ACTION,
-        'file' => 'block.admin.inc',
-        'file path' => drupal_get_path('module', 'block'),
       );
     }
   }
+
+  $items['admin/structure/custom-blocks'] = array(
+    'title' => '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',
+  );
+  $items['block/%custom_block_machine_name'] = array(
+    'title callback' => 'entity_page_label',
+    'title arguments' => array(1),
+    'page callback' => 'custom_block_page_view',
+    'page arguments' => array(1),
+    'access arguments' => array('view custom blocks'),
+    'file' => 'custom_block.pages.inc',
+  );
+  $items['block/%custom_block_machine_name/view'] = array(
+    'title' => 'View',
+    'type' => MENU_DEFAULT_LOCAL_TASK,
+    'weight' => -10,
+  );
+  $items['block/%custom_block_machine_name/edit'] = array(
+    'title' => 'Edit',
+    'page callback' => 'custom_block_edit',
+    'page arguments' => array(1),
+    'access arguments' => array('administer blocks'),
+    'weight' => 0,
+    'type' => MENU_LOCAL_TASK,
+    'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
+    'file' => 'custom_block.pages.inc',
+  );
+  $items['block/%custom_block_machine_name/delete'] = array(
+    'title' => 'Delete',
+    'page callback' => 'drupal_get_form',
+    'page arguments' => array('custom_block_delete_form', 1),
+    'access arguments' => array('administer blocks'),
+    'weight' => 1,
+    'type' => MENU_LOCAL_TASK,
+    'context' => MENU_CONTEXT_INLINE,
+    'file' => 'custom_block.pages.inc',
+  );
   return $items;
 }
 
 /**
+ * Implements hook_permission().
+ */
+function custom_block_permission() {
+  return array(
+    'view custom blocks' => array(
+      'title' => t('View custom blocks on a page'),
+      'description' => t('View custom blocks on a page of their own.')
+    )
+  );
+}
+
+/**
  * Implements hook_theme().
  */
 function custom_block_theme($existing, $type, $theme, $path) {
@@ -53,3 +158,160 @@ 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 $bid
+ *   The bid 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($bid) {
+  return entity_load('custom_block', $bid);
+}
+
+/**
+ * Loads a custom block.
+ *
+ * @param string $machine_name
+ *   The machine name 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_machine_name_load($machine_name) {
+  $block_ids = entity_query('custom_block')->condition('machine_name', $machine_name)->execute();
+  if (!empty($block_ids) && ($block_id = reset($block_ids))) {
+    return entity_load('custom_block', $block_id);
+  }
+  return FALSE;
+}
+
+/**
+ * Entity URI callback.
+ *
+ * @param Drupal\custom_block\Plugin\Core\Entity\CustomBlockType $block_type
+ *   A custom block type entity.
+ *
+ * @return array
+ *   An array with 'path' as the key and the path to the custom block type as
+ *   the value.
+ */
+function custom_block_type_uri(CustomBlockType $block_type) {
+  return array(
+    'path' => 'admin/structure/custom-blocks/manage/' . $block_type->id(),
+  );
+}
+
+/**
+ * Implements hook_entity_info().
+ */
+function custom_block_entity_info(&$types) {
+  // Add a translation handler for fields if the language module is enabled.
+  if (module_exists('language')) {
+    $types['custom_block']['translation']['custom_block'] = TRUE;
+  }
+  foreach (config_get_storage_names_with_prefix('custom_block.type.') as $config_name) {
+    $config = config($config_name);
+    $types['custom_block']['bundles'][$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,
+        'access arguments' => array('administer blocks'),
+      ),
+    );
+  }
+}
+
+/**
+ * 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 the 'full'.
+    entity_get_display('custom_block', $block_type_id, 'full')
+      ->setComponent($field['field_name'], 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) {
+    @list($base, $derivative) = explode(':', $plugin_id);
+    if ($base !== 'custom_block') {
+      continue;
+    }
+    $row['1']['data']['#links']['edit'] = array(
+      'title' => t('Edit'),
+      'href' => 'block/' . $derivative . '/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;
+}
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 0000000..45651ec
--- /dev/null
+++ b/core/modules/block/custom_block/custom_block.pages.inc
@@ -0,0 +1,164 @@
+<?php
+/**
+ * @file
+ * Provides page callbacks for custom blocks.
+ */
+
+use Drupal\custom_block\Plugin\Core\Entity\CustomBlockType;
+use Drupal\custom_block\Plugin\Core\Entity\CustomBlock;
+
+/**
+ * Page callback: Displays a single node.
+ *
+ * @param Drupal\custom_block\Plugin\Core\Entity\CustomBlock $block
+ *   The node entity.
+ *
+ * @return
+ *   A page array suitable for use by drupal_render().
+ *
+ * @see custom_block_menu()
+ */
+function custom_block_page_view(CustomBlock $block) {
+  // If there is a menu link to this node, the link becomes the last part
+  // of the active trail, and the link name becomes the page title.
+  // Thus, we must explicitly set the page title to be the block title.
+  drupal_set_title($block->label());
+  $uri = $block->uri();
+  // Set the block path as the canonical URL to prevent duplicate content.
+  drupal_add_html_head_link(array('rel' => 'canonical', 'href' => url($uri['path'])), TRUE);
+  // Set the non-aliased path as a default shortlink.
+  drupal_add_html_head_link(array('rel' => 'shortlink', 'href' => url($uri['path'], array('alias' => TRUE))), TRUE);
+  return entity_view($block, 'full');
+}
+
+
+/**
+ * Page callback: Displays add custom block links for available types.
+ *
+ * Redirects to block/add/[type] if only one custom block type is available.
+ *
+ * @return array
+ *   A render array for a list of the custom block types that can be added;
+ *   however, if there is only one custom block type defined for the site, the
+ *   function redirects to the custom block add page for that one custom block
+ *   type and does not return at all.
+ *
+ * @see custom_block_menu()
+ */
+function custom_block_add_page() {
+  drupal_set_title(t('Add custom block'));
+  $options = array();
+  if (isset($_GET['theme']) && in_array($_GET['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' => $_GET['theme'])
+    );
+  }
+  $types = config_get_storage_names_with_prefix('custom_block.type.');
+  if (count($types) == 1) {
+    $config = config(reset($types));
+    drupal_goto('block/add/' . $config->get('id'), $options);
+  }
+  // Multiple custom block types exist, present a list of links.
+  $links = array();
+
+  foreach ($types as $config_name) {
+    $config = config($config_name);
+    $links[] = l($config->get('label'), 'block/add/' . $config->get('id'), $options);
+  }
+  return array(
+    '#theme' => array(
+      'item_list',
+      'item_list__custom_block_add'
+    ),
+    '#items' => $links,
+    '#title' => t('Choose a block type to continue.')
+  );
+}
+
+/**
+ * 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();
+  if (isset($_GET['theme']) && in_array($_GET['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($_GET['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/CustomBlockFormController.php b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockFormController.php
new file mode 100644
index 0000000..a1aa4ae
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockFormController.php
@@ -0,0 +1,226 @@
+<?php
+
+/**
+ * @file
+ * Definition of 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 {
+
+  /**
+   * Prepares the custom block object.
+   *
+   * Fills in a few default values, and then invokes hook_custom_block_prepare()
+   * on all modules.
+   *
+   * Overrides Drupal\Core\Entity\EntityFormController::prepareEntity().
+   */
+  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->bid->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('bid', 'vid') 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'))),
+    );
+
+    $form['machine_name'] = array(
+      '#type' => 'machine_name',
+      '#default_value' => $block->machine_name->value,
+      '#machine_name' => array(
+        'exists' => 'custom_block_load',
+        'source' => array('info')
+      ),
+      '#weight' => -4,
+      '#disabled' => !$block->isNew(),
+    );
+
+    $language_configuration = module_invoke('language', 'get_default_configuration', 'custom_block', $block->type->value);
+    $form['langcode'] = array(
+      '#title' => t('Language'),
+      '#type' => 'language_select',
+      '#default_value' => $block->langcode->value,
+      '#languages' => LANGUAGE_ALL,
+      '#access' => isset($language_configuration['language_hidden']) && !$language_configuration['language_hidden'],
+    );
+
+    $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' => !empty($block->log->value) ? $block->log->value : '',
+      '#description' => t('Briefly describe the changes you have made.'),
+    );
+
+    return parent::form($form, $form_state, $block);
+  }
+
+  /**
+   * 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.
+   *
+   * Overrides Drupal\Core\Entity\EntityFormController::submit().
+   */
+  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();
+    }
+
+    foreach (module_implements('custom_block_submit') as $module) {
+      $function = $module . '_custom_block_submit';
+      $function($block, $form, $form_state);
+    }
+
+    return $block;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::save().
+   */
+  public function save(array $form, array &$form_state) {
+    $block = $this->getEntity($form_state);
+    $insert = empty($block->bid->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->bid->value) {
+      $form_state['values']['bid'] = $block->bid->value;
+      $form_state['bid'] = $block->bid->value;
+      if ($insert) {
+        if ($theme = $block->getTheme()) {
+          $form_state['redirect'] = 'admin/structure/block/manage/custom_block:' . $block->machine_name->value . '/' . $theme;
+        }
+        else {
+          $form_state['redirect'] = 'admin/structure/block/manage/custom_block:' . $block->machine_name->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->machine_name->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 0000000..eb227d3
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockRenderController.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Definition of Drupal\custom_block\CustomBlockRenderController.
+ */
+
+namespace Drupal\custom_block;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityRenderController;
+
+/**
+ * Render controller for custom blocks.
+ */
+class CustomBlockRenderController extends EntityRenderController {
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityRenderController::alterBuild().
+   */
+  protected function alterBuild(array &$build, EntityInterface $entity, $view_mode, $langcode = NULL) {
+    parent::alterBuild($build, $entity, $view_mode, $langcode);
+    // Add contextual links for this custom block, except when the block.
+    if (!empty($entity->bid) && $view_mode == 'full') {
+      $build['#contextual_links']['custom_block'] = array('custom_block', array($entity->machine_name));
+    }
+  }
+
+}
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 0000000..30f5265
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockStorageController.php
@@ -0,0 +1,108 @@
+<?php
+
+/**
+ * @file
+ * Definition of 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::postSave().
+   */
+  function postSave(EntityInterface $block, $update) {
+    // Invalidate the block cache to update custom block-based derivatives.
+    if (module_exists('block')) {
+      drupal_container()->get('plugin.manager.block')->clearCachedDefinitions();
+    }
+  }
+
+  /**
+   * Implements \Drupal\Core\Entity\DataBaseStorageControllerNG::basePropertyDefinitions().
+   */
+  public function baseFieldDefinitions() {
+    $properties['bid'] = 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['vid'] = 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['machine_name'] = array(
+      'label' => t('Machine name'),
+      'description' => t('The custom block machine 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 0000000..860aef6
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTranslationController.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Definition of 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()));
+  }
+
+  /**
+   * Overrides EntityTranslationController::entityFormTitle().
+   *
+   * Custom Block menu paths are keyed by machine name, not id.
+   */
+  protected function getPathInstance($path, $entity_id) {
+    $entity = entity_load('custom_block', $entity_id);
+    $wildcard = '%custom_block_machine_name';
+    return str_replace($wildcard, $entity->machine_name->value, $path);
+  }
+}
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 0000000..75484f9
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeFormController.php
@@ -0,0 +1,101 @@
+<?php
+
+/**
+ * @file
+ * Definition of 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['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.')
+    );
+
+    // @todo Remove this check once language settings are generalized.
+    if (module_exists('translation_entity')) {
+      $translation_form = $form;
+      $translation_form_state['translation_entity']['key'] = 'language_configuration';
+      $form += translation_entity_enable_widget('custom_block', $block_type->id(), $translation_form, $translation_form_state);
+    }
+
+    $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'));
+    }
+
+    if (module_exists('translation_entity')) {
+      $key = 'language_configuration';
+      $translation_form_state = array(
+        'translation_entity' => array('key' => $key),
+        'language' => array($key => array('entity_type' => 'custom_block', 'bundle' => $block_type->id())),
+        'values' => array($key => array('translation_entity' => $form_state['values']['translation_entity'])),
+      );
+      translation_entity_language_configuration_element_submit($form, $translation_form_state);
+    }
+    $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 0000000..a59799e
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/CustomBlockTypeListController.php
@@ -0,0 +1,58 @@
+<?php
+/**
+ * @file
+ * Definition of 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' => 11,
+      );
+      $operations['manage-display'] = array(
+        'title' => t('Manage display'),
+        'href' => $uri['path'] . '/display',
+        'options' => $uri['options'],
+        'weight' => 12,
+      );
+    }
+    return $operations;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityListController::buildHeader().
+   */
+  public function buildHeader() {
+    $row['type'] = t('Block type');
+    $row['operations'] = t('Operations');
+    return $row;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityListController::buildRow().
+   */
+  public function buildRow(EntityInterface $entity) {
+    $row['type'] = check_plain($entity->label());
+    $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 0000000..2e27b39
--- /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());
+    }
+    elseif ($entity->original->id() != $entity->id()) {
+      field_attach_rename_bundle('custom_block', $entity->original->id(), $entity->id());
+    }
+    custom_block_add_body_field($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 0000000..aaba53d
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlock.php
@@ -0,0 +1,224 @@
+<?php
+
+/**
+ * @file
+ * Definition of 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",
+ *   render_controller_class = "Drupal\custom_block\CustomBlockRenderController",
+ *   form_controller_class = {
+ *     "default" = "Drupal\custom_block\CustomBlockFormController"
+ *   },
+ *   translation_controller_class = "Drupal\custom_block\CustomBlockTranslationController",
+ *   base_table = "block_custom",
+ *   revision_table = "block_custom_revision",
+ *   uri_callback = "custom_block_uri",
+ *   menu_base_path = "block/%custom_block_machine_name",
+ *   fieldable = TRUE,
+ *   entity_keys = {
+ *     "id" = "bid",
+ *     "revision" = "vid",
+ *     "bundle" = "type",
+ *     "label" = "info",
+ *     "uuid" = "uuid"
+ *   },
+ *   bundle_keys = {
+ *     "bundle" = "type"
+ *   },
+ *   view_modes = {
+ *     "full" = {
+ *       "label" = "Full content",
+ *       "custom_settings" = FALSE
+ *     },
+ *   }
+ * )
+ */
+class CustomBlock extends EntityNG implements ContentEntityInterface {
+
+  /**
+   * The block ID.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $bid;
+
+  /**
+   * The block revision ID.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $vid;
+
+  /**
+   * 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 custom block machine name.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $machine_name;
+
+  /**
+   * The block language code.
+   *
+   * @var \Drupal\Core\Entity\Field\FieldInterface
+   */
+  public $langcode = LANGUAGE_NOT_SPECIFIED;
+
+  /**
+   * 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->bid->value;
+  }
+
+  /**
+   * Implements Drupal\Core\Entity\EntityInterface::bundle().
+   */
+  public function bundle() {
+    return $this->type->value;
+  }
+
+  /**
+   * Implements Drupal\Core\Entity\EntityInterface::label().
+   */
+  public function label($langcode = NULL) {
+    return $this->info->value;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\Entity::createDuplicate().
+   */
+  public function createDuplicate() {
+    $duplicate = parent::createDuplicate();
+    $duplicate->set('vid', NULL);
+    $duplicate->set('bid', NULL);
+    $duplicate->set('machine_name', NULL);
+    return $duplicate;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\Entity::getRevisionId().
+   */
+  public function getRevisionId() {
+    return $this->vid->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;
+  }
+
+  /**
+   * The plain data values of the contained properties.
+   *
+   * Define default values.
+   *
+   * @var array
+   */
+  protected $values = array();
+
+  /**
+   * Initialize the object. Invoked upon construction and wake up.
+   */
+  protected function init() {
+    parent::init();
+    // We unset all defined properties, so magic getters apply.
+    unset($this->bid);
+    unset($this->info);
+    unset($this->vid);
+    unset($this->log);
+    unset($this->machine_name);
+    unset($this->uuid);
+    unset($this->langcode);
+    unset($this->type);
+    unset($this->new);
+  }
+}
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 0000000..fb6f2b5
--- /dev/null
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/Core/Entity/CustomBlockType.php
@@ -0,0 +1,65 @@
+<?php
+
+/**
+ * @file
+ * Definition of 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"
+ *   },
+ *   uri_callback = "custom_block_type_uri",
+ *   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;
+
+}
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 9ff4611..cce38b2 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', entity_query('custom_block')->execute());
+    foreach ($custom_blocks as $custom_block) {
+      $this->derivatives[$custom_block->machine_name->value] = $base_plugin_definition;
+      $this->derivatives[$custom_block->machine_name->value]['settings'] = array(
+        'info' => $custom_block->info->value,
       ) + $base_plugin_definition['settings'];
-      $this->derivatives[$result->bid]['subject'] = $result->info;
+      $this->derivatives[$custom_block->machine_name->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
index 8b142ef..f472ff7 100644
--- a/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlock.php
+++ b/core/modules/block/custom_block/lib/Drupal/custom_block/Plugin/block/block/CustomBlock.php
@@ -1,6 +1,6 @@
 <?php
-
 /**
+ * @file
  * Contains \Drupal\custom_block\Plugin\block\block\CustomBlock.
  */
 
@@ -21,8 +21,7 @@
  *  settings = {
  *    "status" = TRUE,
  *    "info" = "",
- *    "body" = "",
- *    "format" = NULL
+ *    "view_mode" = "full"
  *   }
  * )
  */
@@ -36,6 +35,7 @@ public function blockSettings() {
     // (which is "Custom Block").
     return array(
       'subject' => '',
+      'view_mode' => 'full'
     );
   }
 
@@ -47,8 +47,7 @@ public function getConfig() {
     $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'];
+    $this->configuration['view_mode'] = $definition['settings']['view_mode'];
     return $this->configuration;
   }
 
@@ -58,25 +57,17 @@ public function getConfig() {
    * 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['custom_block']['info'] = array(
-      '#type' => 'textfield',
-      '#title' => t('Block 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['custom_block']['body'] = array(
-      '#type' => 'text_format',
-      '#title' => t('Block 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,
+    $view_modes = array();
+    $info = entity_get_info('custom_block');
+    foreach ($info['view_modes'] as $view_mode => $detail) {
+      $view_modes[$view_mode] = $detail['label'];
+    }
+    $form['custom_block']['view_mode'] = array(
+      '#type' => 'select',
+      '#options' => $view_modes,
+      '#title' => t('View mode'),
+      '#description' => t('Output the block in this view mode.'),
+      '#default_value' => $this->configuration['view_mode']
     );
     $form['custom_block']['title']['#description'] = t('The title of the block as shown to the user.');
     return $form;
@@ -86,15 +77,6 @@ public function blockForm($form, &$form_state) {
    * 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());
-    $this->configuration['id'] = '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();
@@ -105,12 +87,19 @@ public function blockSubmit($form, &$form_state) {
    * Implements \Drupal\block\BlockBase::blockBuild().
    */
   public function blockBuild() {
-    // Populate the block with the user-defined block body.
-    return array(
-      '#theme' => 'custom_block_block',
-      '#body' => $this->configuration['body'],
-      '#format' => $this->configuration['format'],
-    );
+    list(, $machine_name) = explode(':', $this->getPluginId());
+    if ($block = custom_block_machine_name_load($machine_name)) {
+      return entity_view($block, $this->configuration['view_mode']);
+    }
+    else {
+      return array(
+        '#markup' => t('Block with name %name does not exist. <a href="!url">Add custom block</a>.', array(
+          '%name' => $block_id,
+          '!url' => url('block/add')
+        )),
+        '#access' => user_access('administer blocks')
+      );
+    }
   }
 
 }
diff --git a/core/modules/block/lib/Drupal/block/Plugin/system/plugin_ui/BlockPluginUI.php b/core/modules/block/lib/Drupal/block/Plugin/system/plugin_ui/BlockPluginUI.php
index 27256a2..61f3dd4 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);
