diff --git a/core/includes/install.inc b/core/includes/install.inc
index aada754..999a02b 100644
--- a/core/includes/install.inc
+++ b/core/includes/install.inc
@@ -429,6 +429,10 @@ function drupal_install_system() {
   module_list_reset();
   module_implements_reset();
 
+  // @todo PHP 5.3.9 changes the behavior of is_subclass_of() with respect to
+  //   autoloaders. To allow earlier versions to properly find system.module,
+  //   warm the module list cache.
+  module_list();
   config_install_default_config('module', 'system');
 
   module_invoke('system', 'install');
diff --git a/core/includes/menu.inc b/core/includes/menu.inc
index 7d419ac..e6810b8 100644
--- a/core/includes/menu.inc
+++ b/core/includes/menu.inc
@@ -2641,7 +2641,6 @@ function menu_reset_static_cache() {
   drupal_static_reset('menu_tree');
   drupal_static_reset('menu_tree_all_data');
   drupal_static_reset('menu_tree_page_data');
-  drupal_static_reset('menu_load_all');
   drupal_static_reset('menu_link_get_preferred');
 }
 
diff --git a/core/modules/block/block.module b/core/modules/block/block.module
index a31feec..d9155c3 100644
--- a/core/modules/block/block.module
+++ b/core/modules/block/block.module
@@ -662,7 +662,7 @@ function block_menu_delete($menu) {
   $block_configs = config_get_storage_names_with_prefix('plugin.core.block');
   foreach ($block_configs as $config_id) {
     $config = config($config_id);
-    if ($config->get('id') == 'menu_menu_block:' . $menu['menu_name']) {
+    if ($config->get('id') == 'menu_menu_block:' . $menu->id()) {
       $config->delete();
     }
   }
diff --git a/core/modules/field_ui/field_ui.module b/core/modules/field_ui/field_ui.module
index a04c0dd..dd2a1cc 100644
--- a/core/modules/field_ui/field_ui.module
+++ b/core/modules/field_ui/field_ui.module
@@ -76,8 +76,8 @@ function field_ui_menu() {
           // Extract path information from the bundle.
           $path = $bundle_info['admin']['path'];
           // Different bundles can appear on the same path (e.g. %node_type and
-          // %comment_node_type). To allow field_ui_menu_load() to extract the
-          // actual bundle object from the translated menu router path
+          // %comment_node_type). To allow field_ui_instance_load() to extract
+          // the actual bundle object from the translated menu router path
           // arguments, we need to identify the argument position of the bundle
           // name string ('bundle argument') and pass that position to the menu
           // loader. The position needs to be casted into a string; otherwise it
@@ -90,7 +90,7 @@ function field_ui_menu() {
             $bundle_arg = $bundle_name;
             $bundle_pos = '0';
           }
-          // This is the position of the %field_ui_menu placeholder in the
+          // This is the position of the %field_ui_instance placeholder in the
           // items below.
           $field_position = count(explode('/', $path)) + 1;
 
@@ -109,15 +109,15 @@ function field_ui_menu() {
             'weight' => 1,
             'file' => 'field_ui.admin.inc',
           ) + $access;
-          $items["$path/fields/%field_ui_menu"] = array(
+          $items["$path/fields/%field_ui_instance"] = array(
             'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
-            'title callback' => 'field_ui_menu_title',
+            'title callback' => 'field_ui_instance_title',
             'title arguments' => array($field_position),
             'page callback' => 'drupal_get_form',
             'page arguments' => array('field_ui_field_edit_form', $field_position),
             'file' => 'field_ui.admin.inc',
           ) + $access;
-          $items["$path/fields/%field_ui_menu/edit"] = array(
+          $items["$path/fields/%field_ui_instance/edit"] = array(
             'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
             'title' => 'Edit',
             'page callback' => 'drupal_get_form',
@@ -125,7 +125,7 @@ function field_ui_menu() {
             'type' => MENU_DEFAULT_LOCAL_TASK,
             'file' => 'field_ui.admin.inc',
           ) + $access;
-          $items["$path/fields/%field_ui_menu/field-settings"] = array(
+          $items["$path/fields/%field_ui_instance/field-settings"] = array(
             'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
             'title' => 'Field settings',
             'page callback' => 'drupal_get_form',
@@ -133,7 +133,7 @@ function field_ui_menu() {
             'type' => MENU_LOCAL_TASK,
             'file' => 'field_ui.admin.inc',
           ) + $access;
-          $items["$path/fields/%field_ui_menu/widget-type"] = array(
+          $items["$path/fields/%field_ui_instance/widget-type"] = array(
             'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
             'title' => 'Widget type',
             'page callback' => 'drupal_get_form',
@@ -141,7 +141,7 @@ function field_ui_menu() {
             'type' => MENU_LOCAL_TASK,
             'file' => 'field_ui.admin.inc',
           ) + $access;
-          $items["$path/fields/%field_ui_menu/delete"] = array(
+          $items["$path/fields/%field_ui_instance/delete"] = array(
             'load arguments' => array($entity_type, $bundle_arg, $bundle_pos, '%map'),
             'title' => 'Delete',
             'page callback' => 'drupal_get_form',
@@ -211,12 +211,12 @@ function field_ui_menu() {
  *
  * @ingroup field
  */
-function field_ui_menu_load($field_name, $entity_type, $bundle_name, $bundle_pos, $map) {
+function field_ui_instance_load($field_name, $entity_type, $bundle_name, $bundle_pos, $map) {
   // Extract the actual bundle name from the translated argument map.
   // The menu router path to manage fields of an entity can be shared among
   // multiple bundles. For example:
-  // - admin/structure/types/manage/%node_type/fields/%field_ui_menu
-  // - admin/structure/types/manage/%comment_node_type/fields/%field_ui_menu
+  // - admin/structure/types/manage/%node_type/fields/%field_ui_instance
+  // - admin/structure/types/manage/%comment_node_type/fields/%field_ui_instance
   // The menu system will automatically load the correct bundle depending on the
   // actual path arguments, but this menu loader function only receives the node
   // type string as $bundle_name, which is not the bundle name for comments.
@@ -242,7 +242,7 @@ function field_ui_menu_load($field_name, $entity_type, $bundle_name, $bundle_pos
  *
  * @see field_ui_menu()
  */
-function field_ui_menu_title($instance) {
+function field_ui_instance_title($instance) {
   return $instance['label'];
 }
 
diff --git a/core/modules/menu/config/menu.menu.account.yml b/core/modules/menu/config/menu.menu.account.yml
new file mode 100644
index 0000000..6be2706
--- /dev/null
+++ b/core/modules/menu/config/menu.menu.account.yml
@@ -0,0 +1,3 @@
+id: account
+label: User account menu
+description: Links related to the user account.
diff --git a/core/modules/menu/config/menu.menu.admin.yml b/core/modules/menu/config/menu.menu.admin.yml
new file mode 100644
index 0000000..5435da5
--- /dev/null
+++ b/core/modules/menu/config/menu.menu.admin.yml
@@ -0,0 +1,3 @@
+id: admin
+label: Administration
+description: Contains links to administrative tasks.
diff --git a/core/modules/menu/config/menu.menu.footer.yml b/core/modules/menu/config/menu.menu.footer.yml
new file mode 100644
index 0000000..9dda784
--- /dev/null
+++ b/core/modules/menu/config/menu.menu.footer.yml
@@ -0,0 +1,3 @@
+id: footer
+label: Footer
+description: Use this for linking to site information.
diff --git a/core/modules/menu/config/menu.menu.main.yml b/core/modules/menu/config/menu.menu.main.yml
new file mode 100644
index 0000000..3dfe975
--- /dev/null
+++ b/core/modules/menu/config/menu.menu.main.yml
@@ -0,0 +1,3 @@
+id: main
+label: Main navigation
+description: Use this for linking to the main site sections.
diff --git a/core/modules/menu/config/menu.menu.tools.yml b/core/modules/menu/config/menu.menu.tools.yml
new file mode 100644
index 0000000..3f15287
--- /dev/null
+++ b/core/modules/menu/config/menu.menu.tools.yml
@@ -0,0 +1,3 @@
+id: tools
+label: Tools
+description: Contains links for site visitors. Some modules add their links here.
diff --git a/core/modules/menu/lib/Drupal/menu/MenuFormController.php b/core/modules/menu/lib/Drupal/menu/MenuFormController.php
new file mode 100644
index 0000000..41e5696
--- /dev/null
+++ b/core/modules/menu/lib/Drupal/menu/MenuFormController.php
@@ -0,0 +1,103 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\menu\MenuFormController.
+ */
+
+namespace Drupal\menu;
+
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Entity\EntityFormController;
+
+/**
+ * Base form controller for menu edit forms.
+ */
+class MenuFormController extends EntityFormController {
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::form().
+   */
+  public function form(array $form, array &$form_state, EntityInterface $menu) {
+    $form = parent::form($form, $form_state, $menu);
+    $system_menus = menu_list_system_menus();
+
+    $form['label'] = array(
+      '#type' => 'textfield',
+      '#title' => t('Title'),
+      '#default_value' => $menu->label(),
+      '#required' => TRUE,
+      // The title of a system menu cannot be altered.
+      '#access' => !isset($system_menus[$menu->id()]),
+    );
+    $form['id'] = array(
+      '#type' => 'machine_name',
+      '#title' => t('Menu name'),
+      '#default_value' => $menu->id(),
+      '#maxlength' => MENU_MAX_MENU_NAME_LENGTH_UI,
+      '#description' => t('A unique name to construct the URL for the menu. It must only contain lowercase letters, numbers and hyphens.'),
+      '#machine_name' => array(
+        'exists' => 'menu_edit_menu_name_exists',
+        'source' => array('label'),
+        'replace_pattern' => '[^a-z0-9-]+',
+        'replace' => '-',
+      ),
+      // A menu's machine name cannot be changed.
+      '#disabled' => !$menu->isNew() || isset($system_menus[$menu->id()]),
+    );
+    $form['description'] = array(
+      '#type' => 'textarea',
+      '#title' => t('Description'),
+      '#default_value' => $menu->description,
+    );
+    $form['actions'] = array('#type' => 'actions');
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save'),
+      '#button_type' => 'primary',
+    );
+    // Only custom menus may be deleted.
+    $form['actions']['delete'] = array(
+      '#type' => 'submit',
+      '#value' => t('Delete'),
+      '#access' => !$menu->isNew() && !isset($system_menus[$menu->id()]),
+    );
+
+    return $form;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::save().
+   */
+  public function save(array $form, array &$form_state) {
+    $menu = $this->getEntity($form_state);
+
+    if ($menu->isNew()) {
+      // Add 'menu-' to the menu name to help avoid name-space conflicts.
+      $menu->set('id', 'menu-' . $menu->id());
+    }
+
+    $status = $menu->save();
+
+    $uri = $menu->uri();
+    if ($status == SAVED_UPDATED) {
+      drupal_set_message(t('Menu %label has been updated.', array('%label' => $menu->label())));
+      watchdog('menu', 'Menu %label has been updated.', array('%label' => $menu->label()), WATCHDOG_NOTICE, l(t('Edit'), $uri['path'] . '/edit'));
+    }
+    else {
+      drupal_set_message(t('Menu %label has been added.', array('%label' => $menu->label())));
+      watchdog('menu', 'Menu %label has been added.', array('%label' => $menu->label()), WATCHDOG_NOTICE, l(t('Edit'), $uri['path'] . '/edit'));
+    }
+
+    $form_state['redirect'] = 'admin/structure/menu/manage/' . $menu->id();
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::delete().
+   */
+  public function delete(array $form, array &$form_state) {
+    $menu = $this->getEntity($form_state);
+    $form_state['redirect'] = 'admin/structure/menu/manage/' . $menu->id() . '/delete';
+  }
+
+}
diff --git a/core/modules/menu/lib/Drupal/menu/MenuListController.php b/core/modules/menu/lib/Drupal/menu/MenuListController.php
new file mode 100644
index 0000000..a833dbc
--- /dev/null
+++ b/core/modules/menu/lib/Drupal/menu/MenuListController.php
@@ -0,0 +1,83 @@
+<?php
+
+/**
+ * Contains \Drupal\menu\MenuListController.
+ */
+
+namespace Drupal\menu;
+
+use Drupal\Core\Config\Entity\ConfigEntityListController;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Provides a listing of contact categories.
+ */
+class MenuListController extends ConfigEntityListController {
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityListController::buildHeader().
+   */
+  public function buildHeader() {
+    $row['title'] = t('Title');
+    $row['description'] = array(
+      'data' => t('Description'),
+      'class' => array(RESPONSIVE_PRIORITY_MEDIUM),
+    );
+    $row['operations'] = t('Operations');
+    return $row;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityListController::buildRow().
+   */
+  public function buildRow(EntityInterface $entity) {
+    $row['title'] = array(
+      'data' => check_plain($entity->label()),
+      'class' => array('menu-label'),
+    );
+    $row['description'] = filter_xss_admin($entity->description);
+    $row['operations']['data'] = $this->buildOperations($entity);
+    return $row;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityListController::getOperations();
+   */
+  public function getOperations(EntityInterface $entity) {
+    $operations = parent::getOperations($entity);
+    $uri = $entity->uri();
+
+    $operations['list'] = array(
+      'title' => t('list links'),
+      'href' => $uri['path'],
+      'options' => $uri['options'],
+      'weight' => 0,
+    );
+    $operations['edit']['title'] = t('edit menu');
+    $operations['add'] = array(
+      'title' => t('add link'),
+      'href' => $uri['path'] . '/add',
+      'options' => $uri['options'],
+      'weight' => 20,
+    );
+    // System menus could not be deleted.
+    $system_menus = menu_list_system_menus();
+    if (isset($system_menus[$entity->id()])) {
+      unset($operations['delete']);
+    }
+    else {
+      $operations['delete']['title'] = t('delete menu');
+    }
+    return $operations;
+  }
+
+  /**
+   * Overrides \Drupal\Core\Entity\EntityListController::render();
+   */
+  public function render() {
+    $build = parent::render();
+    $build['#attached']['css'][] = drupal_get_path('module', 'menu') . '/menu.admin.css';
+    return $build;
+  }
+
+}
diff --git a/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php
index caf27e6..a0d8d28 100644
--- a/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php
+++ b/core/modules/menu/lib/Drupal/menu/Tests/MenuTest.php
@@ -102,8 +102,8 @@ function doStandardMenuTests() {
    */
   function doCustomMenuTests() {
     $this->menu = $this->addCustomMenu();
-    $this->doMenuTests($this->menu['menu_name']);
-    $this->addInvalidMenuLink($this->menu['menu_name']);
+    $this->doMenuTests($this->menu->id());
+    $this->addInvalidMenuLink($this->menu->id());
     $this->addCustomMenuCRUD();
   }
 
@@ -113,25 +113,25 @@ function doCustomMenuTests() {
   function addCustomMenuCRUD() {
     // Add a new custom menu.
     $menu_name = substr(hash('sha256', $this->randomName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI);
-    $title = $this->randomName(16);
+    $label = $this->randomName(16);
 
-    $menu = array(
-      'menu_name' => $menu_name,
-      'title' => $title,
+    $menu = entity_create('menu', array(
+      'id' => $menu_name,
+      'label' => $label,
       'description' => 'Description text',
-    );
-    menu_save($menu);
+    ));
+    $menu->save();
 
     // Assert the new menu.
     $this->drupalGet('admin/structure/menu/manage/' . $menu_name . '/edit');
-    $this->assertRaw($title, 'Custom menu was added.');
+    $this->assertRaw($label, 'Custom menu was added.');
 
     // Edit the menu.
-    $new_title = $this->randomName(16);
-    $menu['title'] = $new_title;
-    menu_save($menu);
+    $new_label = $this->randomName(16);
+    $menu->set('label', $new_label);
+    $menu->save();
     $this->drupalGet('admin/structure/menu/manage/' . $menu_name . '/edit');
-    $this->assertRaw($new_title, 'Custom menu was edited.');
+    $this->assertRaw($new_label, 'Custom menu was edited.');
   }
 
   /**
@@ -142,11 +142,11 @@ function addCustomMenu() {
     // Try adding a menu using a menu_name that is too long.
     $this->drupalGet('admin/structure/menu/add');
     $menu_name = substr(hash('sha256', $this->randomName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI + 1);
-    $title = $this->randomName(16);
+    $label = $this->randomName(16);
     $edit = array(
-      'menu_name' => $menu_name,
+      'id' => $menu_name,
       'description' => '',
-      'title' =>  $title,
+      'label' =>  $label,
     );
     $this->drupalPost('admin/structure/menu/add', $edit, t('Save'));
 
@@ -159,7 +159,7 @@ function addCustomMenu() {
 
     // Change the menu_name so it no longer exceeds the maximum length.
     $menu_name = substr(hash('sha256', $this->randomName(16)), 0, MENU_MAX_MENU_NAME_LENGTH_UI);
-    $edit['menu_name'] = $menu_name;
+    $edit['id'] = $menu_name;
     $this->drupalPost('admin/structure/menu/add', $edit, t('Save'));
 
     // Verify that no validation error is given for menu_name length.
@@ -168,16 +168,16 @@ function addCustomMenu() {
       '%max' => MENU_MAX_MENU_NAME_LENGTH_UI,
       '%length' => drupal_strlen($menu_name),
     )));
-    // Unlike most other modules, there is no confirmation message displayed.
-
+    // Verify that confirmation message displayed.
+    $this->assertRaw(t('Menu %label has been added.', array('%label' => $label)));
     $this->drupalGet('admin/structure/menu');
-    $this->assertText($title, 'Menu created');
+    $this->assertText($label, 'Menu created');
 
     // Enable the custom menu block.
     $menu_name = 'menu-' . $menu_name; // Drupal prepends the name with 'menu-'.
     // Confirm that the custom menu block is available.
     $this->drupalGet('admin/structure/block/list/block_plugin_ui:' . variable_get('theme_default', 'stark') . '/add');
-    $this->assertText($title);
+    $this->assertText($label);
 
     // Enable the block.
     $this->drupalPlaceBlock('menu_menu_block:' . $menu_name);
@@ -190,13 +190,13 @@ function addCustomMenu() {
    * @param string $menu_name Custom menu name.
    */
   function deleteCustomMenu($menu) {
-    $menu_name = $this->menu['menu_name'];
-    $title = $this->menu['title'];
+    $menu_name = $this->menu->id();
+    $label = $this->menu->label();
 
     // Delete custom menu.
     $this->drupalPost("admin/structure/menu/manage/$menu_name/delete", array(), t('Delete'));
     $this->assertResponse(200);
-    $this->assertRaw(t('The custom menu %title has been deleted.', array('%title' => $title)), 'Custom menu was deleted');
+    $this->assertRaw(t('The custom menu %title has been deleted.', array('%title' => $label)), 'Custom menu was deleted');
     $this->assertFalse(menu_load($menu_name), 'Custom menu was deleted');
     // Test if all menu links associated to the menu were removed from database.
     $result = db_query("SELECT menu_name FROM {menu_links} WHERE menu_name = :menu_name", array(':menu_name' => $menu_name))->fetchField();
diff --git a/core/modules/menu/menu.admin.css b/core/modules/menu/menu.admin.css
index 8717aca..efbfe75 100644
--- a/core/modules/menu/menu.admin.css
+++ b/core/modules/menu/menu.admin.css
@@ -1,6 +1,6 @@
-.menu-operations {
-  width: 100px;
-}
 .menu-enabled {
   width: 70px;
 }
+.menu-label {
+  font-weight: bold;
+}
diff --git a/core/modules/menu/menu.admin.inc b/core/modules/menu/menu.admin.inc
index 4a78495..1962ce3 100644
--- a/core/modules/menu/menu.admin.inc
+++ b/core/modules/menu/menu.admin.inc
@@ -6,57 +6,42 @@
  */
 
 use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
+use Drupal\system\Plugin\Core\Entity\Menu;
 
 /**
  * Menu callback which shows an overview page of all the custom menus and their descriptions.
  */
 function menu_overview_page() {
-  $result = db_query("SELECT * FROM {menu_custom} ORDER BY title", array(), array('fetch' => PDO::FETCH_ASSOC));
-  $header = array(t('Title'), t('Operations'));
-  $rows = array();
-  foreach ($result as $menu) {
-    $row = array();
-    $row[] = theme('menu_admin_overview', array('title' => $menu['title'], 'name' => $menu['menu_name'], 'description' => $menu['description']));
-    $links = array();
-    $links['list'] = array(
-      'title' => t('list links'),
-      'href' => 'admin/structure/menu/manage/' . $menu['menu_name'],
-    );
-    $links['edit'] = array(
-      'title' => t('edit menu'),
-      'href' => 'admin/structure/menu/manage/' . $menu['menu_name'] . '/edit',
-    );
-    $links['add'] = array(
-      'title' => t('add link'),
-      'href' => 'admin/structure/menu/manage/' . $menu['menu_name'] . '/add',
-    );
-    $row[] = array(
-      'data' => array(
-        '#type' => 'operations',
-        '#links' => $links,
-      ),
-    );
-    $rows[] = $row;
-  }
-
-  return theme('table', array('header' => $header, 'rows' => $rows));
+  return entity_list_controller('menu')->render();
 }
 
 /**
- * Returns HTML for a menu title and description for the menu overview page.
+ * Page callback: Presents the menu creation form.
  *
- * @param $variables
- *   An associative array containing:
- *   - title: The menu's title.
- *   - description: The menu's description.
+ * @return array
+ *   A form array as expected by drupal_render().
  *
- * @ingroup themeable
+ * @see menu_menu()
  */
-function theme_menu_admin_overview($variables) {
-  $output = check_plain($variables['title']);
-  $output .= '<div class="description">' . filter_xss_admin($variables['description']) . '</div>';
+function menu_menu_add() {
+  $menu = entity_create('menu', array());
+  return entity_get_form($menu);
+}
 
-  return $output;
+/**
+ * Page callback: Presents the menu edit form.
+ *
+ * @param \Drupal\system\Plugin\Core\Entity\Menu $menu
+ *   The menu to edit.
+ *
+ * @return array
+ *   A form array as expected by drupal_render().
+ *
+ * @see menu_menu()
+ */
+function menu_menu_edit(Menu $menu) {
+  drupal_set_title(t('Edit menu %label', array('%label' => $menu->label())), PASS_THROUGH);
+  return entity_get_form($menu);
 }
 
 /**
@@ -73,7 +58,7 @@ function menu_overview_form($form, &$form_state, $menu) {
     FROM {menu_links} ml LEFT JOIN {menu_router} m ON m.path = ml.router_path
     WHERE ml.menu_name = :menu
     ORDER BY p1 ASC, p2 ASC, p3 ASC, p4 ASC, p5 ASC, p6 ASC, p7 ASC, p8 ASC, p9 ASC";
-  $result = db_query($sql, array(':menu' => $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
+  $result = db_query($sql, array(':menu' => $menu->id()), array('fetch' => PDO::FETCH_ASSOC));
   $links = array();
   foreach ($result as $item) {
     $links[] = $item;
@@ -99,7 +84,7 @@ function menu_overview_form($form, &$form_state, $menu) {
     );
   }
   else {
-    $form['#empty_text'] = t('There are no menu links yet. <a href="@link">Add link</a>.', array('@link' => url('admin/structure/menu/manage/'. $form['#menu']['menu_name'] .'/add')));
+    $form['#empty_text'] = t('There are no menu links yet. <a href="@link">Add link</a>.', array('@link' => url('admin/structure/menu/manage/'. $form['#menu']->id() .'/add')));
   }
   return $form;
 }
@@ -296,7 +281,7 @@ function theme_menu_overview_form($variables) {
 function menu_edit_item($form, &$form_state, $type, $item, $menu) {
   if ($type == 'add' || empty($item)) {
     // This is an add form, initialize the menu link.
-    $item = array('link_title' => '', 'mlid' => 0, 'plid' => 0, 'menu_name' => $menu['menu_name'], 'weight' => 0, 'link_path' => '', 'options' => array(), 'module' => 'menu', 'expanded' => 0, 'hidden' => 0, 'has_children' => 0);
+    $item = array('link_title' => '', 'mlid' => 0, 'plid' => 0, 'menu_name' => $menu->id(), 'weight' => 0, 'link_path' => '', 'options' => array(), 'module' => 'menu', 'expanded' => 0, 'hidden' => 0, 'has_children' => 0);
   }
   else {
     // Get the human-readable menu title from the given menu name.
@@ -478,85 +463,12 @@ function menu_edit_item_submit($form, &$form_state) {
 }
 
 /**
- * Menu callback; Build the form that handles the adding/editing of a custom menu.
- */
-function menu_edit_menu($form, &$form_state, $type, $menu = array()) {
-  $system_menus = menu_list_system_menus();
-  $menu += array(
-    'menu_name' => '',
-    'old_name' => !empty($menu['menu_name']) ? $menu['menu_name'] : '',
-    'title' => '',
-    'description' => '',
-  );
-  // Allow menu_edit_menu_submit() and other form submit handlers to determine
-  // whether the menu already exists.
-  $form['#insert'] = empty($menu['old_name']);
-  $form['old_name'] = array(
-    '#type' => 'value',
-    '#value' => $menu['old_name'],
-  );
-
-  $form['title'] = array(
-    '#type' => 'textfield',
-    '#title' => t('Title'),
-    '#default_value' => $menu['title'],
-    '#required' => TRUE,
-    // The title of a system menu cannot be altered.
-    '#access' => !isset($system_menus[$menu['menu_name']]),
-  );
-
-  $form['menu_name'] = array(
-    '#type' => 'machine_name',
-    '#title' => t('Menu name'),
-    '#default_value' => $menu['menu_name'],
-    '#maxlength' => MENU_MAX_MENU_NAME_LENGTH_UI,
-    '#description' => t('A unique name to construct the URL for the menu. It must only contain lowercase letters, numbers and hyphens.'),
-    '#machine_name' => array(
-      'exists' => 'menu_edit_menu_name_exists',
-      'source' => array('title'),
-      'replace_pattern' => '[^a-z0-9-]+',
-      'replace' => '-',
-    ),
-    // A menu's machine name cannot be changed.
-    '#disabled' => !empty($menu['old_name']) || isset($system_menus[$menu['menu_name']]),
-  );
-
-  $form['description'] = array(
-    '#type' => 'textarea',
-    '#title' => t('Description'),
-    '#default_value' => $menu['description'],
-  );
-  $form['actions'] = array('#type' => 'actions');
-  $form['actions']['submit'] = array(
-    '#type' => 'submit',
-    '#value' => t('Save'),
-    '#button_type' => 'primary',
-  );
-  // Only custom menus may be deleted.
-  $form['actions']['delete'] = array(
-    '#type' => 'submit',
-    '#value' => t('Delete'),
-    '#access' => $type == 'edit' && !isset($system_menus[$menu['menu_name']]),
-    '#submit' => array('menu_custom_delete_submit'),
-  );
-
-  return $form;
-}
-
-/**
- * Submit function for the 'Delete' button on the menu editing form.
- */
-function menu_custom_delete_submit($form, &$form_state) {
-  $form_state['redirect'] = 'admin/structure/menu/manage/' . $form_state['values']['menu_name'] . '/delete';
-}
-
-/**
  * Menu callback; check access and get a confirm form for deletion of a custom menu.
  */
 function menu_delete_menu_page($menu) {
   // System-defined menus may not be deleted.
   $system_menus = menu_list_system_menus();
-  if (isset($system_menus[$menu['menu_name']])) {
+  if (isset($system_menus[$menu->id()])) {
     throw new AccessDeniedHttpException();
   }
   return drupal_get_form('menu_delete_menu_confirm', $menu);
@@ -565,15 +477,15 @@ function menu_delete_menu_page($menu) {
 /**
  * Build a confirm form for deletion of a custom menu.
  */
-function menu_delete_menu_confirm($form, &$form_state, $menu) {
+function menu_delete_menu_confirm($form, &$form_state, Menu $menu) {
   $form['#menu'] = $menu;
   $caption = '';
-  $num_links = db_query("SELECT COUNT(*) FROM {menu_links} WHERE menu_name = :menu", array(':menu' => $menu['menu_name']))->fetchField();
+  $num_links = db_query("SELECT COUNT(*) FROM {menu_links} WHERE menu_name = :menu", array(':menu' => $menu->id()))->fetchField();
   if ($num_links) {
-    $caption .= '<p>' . format_plural($num_links, '<strong>Warning:</strong> There is currently 1 menu link in %title. It will be deleted (system-defined items will be reset).', '<strong>Warning:</strong> There are currently @count menu links in %title. They will be deleted (system-defined links will be reset).', array('%title' => $menu['title'])) . '</p>';
+    $caption .= '<p>' . format_plural($num_links, '<strong>Warning:</strong> There is currently 1 menu link in %title. It will be deleted (system-defined items will be reset).', '<strong>Warning:</strong> There are currently @count menu links in %title. They will be deleted (system-defined links will be reset).', array('%title' => $menu->label())) . '</p>';
   }
   $caption .= '<p>' . t('This action cannot be undone.') . '</p>';
-  return confirm_form($form, t('Are you sure you want to delete the custom menu %title?', array('%title' => $menu['title'])), 'admin/structure/menu/manage/' . $menu['menu_name'], $caption, t('Delete'));
+  return confirm_form($form, t('Are you sure you want to delete the custom menu %title?', array('%title' => $menu->label())), 'admin/structure/menu/manage/' . $menu->id(), $caption, t('Delete'));
 }
 
 /**
@@ -585,26 +497,26 @@ function menu_delete_menu_confirm_submit($form, &$form_state) {
 
   // System-defined menus may not be deleted - only menus defined by this module.
   $system_menus = menu_list_system_menus();
-  if (isset($system_menus[$menu['menu_name']])  || !(db_query("SELECT 1 FROM {menu_custom} WHERE menu_name = :menu", array(':menu' => $menu['menu_name']))->fetchField())) {
+  if (isset($system_menus[$menu->id()])) {
     return;
   }
 
   // Reset all the menu links defined by the system via hook_menu().
-  $result = db_query("SELECT * FROM {menu_links} ml INNER JOIN {menu_router} m ON ml.router_path = m.path WHERE ml.menu_name = :menu AND ml.module = 'system' ORDER BY m.number_parts ASC", array(':menu' => $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
+  $result = db_query("SELECT * FROM {menu_links} ml INNER JOIN {menu_router} m ON ml.router_path = m.path WHERE ml.menu_name = :menu AND ml.module = 'system' ORDER BY m.number_parts ASC", array(':menu' => $menu->id()), array('fetch' => PDO::FETCH_ASSOC));
   foreach ($result as $link) {
     menu_reset_item($link);
   }
 
   // Delete all links to the overview page for this menu.
-  $result = db_query("SELECT mlid FROM {menu_links} ml WHERE ml.link_path = :link", array(':link' => 'admin/structure/menu/manage/' . $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
+  $result = db_query("SELECT mlid FROM {menu_links} ml WHERE ml.link_path = :link", array(':link' => 'admin/structure/menu/manage/' . $menu->id()), array('fetch' => PDO::FETCH_ASSOC));
   foreach ($result as $link) {
     menu_link_delete($link['mlid']);
   }
 
   // Delete the custom menu and all its menu links.
-  menu_delete($menu);
+  $menu->delete();
 
-  $t_args = array('%title' => $menu['title']);
+  $t_args = array('%title' => $menu->label());
   drupal_set_message(t('The custom menu %title has been deleted.', $t_args));
   watchdog('menu', 'Deleted custom menu %title and all its menu links.', $t_args, WATCHDOG_NOTICE);
 }
@@ -616,9 +528,9 @@ function menu_delete_menu_confirm_submit($form, &$form_state) {
  * @see form_validate_machine_name()
  */
 function menu_edit_menu_name_exists($value) {
+  $custom_exists = entity_load('menu', $value);
   // 'menu-' is added to the menu name to avoid name-space conflicts.
   $value = 'menu-' . $value;
-  $custom_exists = db_query_range('SELECT 1 FROM {menu_custom} WHERE menu_name = :menu', 0, 1, array(':menu' => $value))->fetchField();
   $link_exists = db_query_range("SELECT 1 FROM {menu_links} WHERE menu_name = :menu", 0, 1, array(':menu' => $value))->fetchField();
 
   return $custom_exists || $link_exists;
@@ -632,9 +544,9 @@ function menu_edit_menu_submit($form, &$form_state) {
   $path = 'admin/structure/menu/manage/';
   if ($form['#insert']) {
     // Add 'menu-' to the menu name to help avoid name-space conflicts.
-    $menu['menu_name'] = 'menu-' . $menu['menu_name'];
-    $link['link_title'] = $menu['title'];
-    $link['link_path'] = $path . $menu['menu_name'];
+    $menu['id'] = 'menu-' . $menu['id'];
+    $link['link_title'] = $menu['label'];
+    $link['link_path'] = $path . $menu['id'];
     $link['router_path'] = $path . '%';
     $link['module'] = 'menu';
     $link['plid'] = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :link AND module = :module", array(
@@ -648,15 +560,15 @@ function menu_edit_menu_submit($form, &$form_state) {
   }
   else {
     menu_save($menu);
-    $result = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :path", array(':path' => $path . $menu['menu_name']), array('fetch' => PDO::FETCH_ASSOC));
+    $result = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :path", array(':path' => $path . $menu['id']), array('fetch' => PDO::FETCH_ASSOC));
     foreach ($result as $m) {
       $link = menu_link_load($m['mlid']);
-      $link['link_title'] = $menu['title'];
+      $link['link_title'] = $menu['label'];
       menu_link_save($link);
     }
   }
   drupal_set_message(t('Your configuration has been saved.'));
-  $form_state['redirect'] = $path . $menu['menu_name'];
+  $form_state['redirect'] = $path . $menu['id'];
 }
 
 /**
diff --git a/core/modules/menu/menu.api.php b/core/modules/menu/menu.api.php
index 3f3818e..95926a6 100644
--- a/core/modules/menu/menu.api.php
+++ b/core/modules/menu/menu.api.php
@@ -17,11 +17,8 @@
  * Contributed modules may use the information to perform actions based on the
  * information entered into the menu system.
  *
- * @param $menu
- *   An array representing a custom menu:
- *   - menu_name: The unique name of the custom menu.
- *   - title: The human readable menu title.
- *   - description: The custom menu description.
+ * @param \Drupal\system\Plugin\Core\Entity\Menu $menu
+ *   A menu entity.
  *
  * @see hook_menu_update()
  * @see hook_menu_delete()
@@ -29,7 +26,7 @@
 function hook_menu_insert($menu) {
   // For example, we track available menus in a variable.
   $my_menus = variable_get('my_module_menus', array());
-  $my_menus[$menu['menu_name']] = $menu['menu_name'];
+  $my_menus[$menu->id()] = $menu->id();
   variable_set('my_module_menus', $my_menus);
 }
 
@@ -40,13 +37,8 @@ function hook_menu_insert($menu) {
  * Contributed modules may use the information to perform actions based on the
  * information entered into the menu system.
  *
- * @param $menu
- *   An array representing a custom menu:
- *   - menu_name: The unique name of the custom menu.
- *   - title: The human readable menu title.
- *   - description: The custom menu description.
- *   - old_name: The current 'menu_name'. Note that internal menu names cannot
- *     be changed after initial creation.
+ * @param \Drupal\system\Plugin\Core\Entity\Menu $menu
+ *   A menu entity.
  *
  * @see hook_menu_insert()
  * @see hook_menu_delete()
@@ -54,7 +46,7 @@ function hook_menu_insert($menu) {
 function hook_menu_update($menu) {
   // For example, we track available menus in a variable.
   $my_menus = variable_get('my_module_menus', array());
-  $my_menus[$menu['menu_name']] = $menu['menu_name'];
+  $my_menus[$menu->id()] = $menu->id();
   variable_set('my_module_menus', $my_menus);
 }
 
@@ -66,11 +58,8 @@ function hook_menu_update($menu) {
  * information to perform actions based on the information entered into the menu
  * system.
  *
- * @param $link
- *   An array representing a custom menu:
- *   - menu_name: The unique name of the custom menu.
- *   - title: The human readable menu title.
- *   - description: The custom menu description.
+ * @param \Drupal\system\Plugin\Core\Entity\Menu $menu
+ *   A menu entity.
  *
  * @see hook_menu_insert()
  * @see hook_menu_update()
@@ -78,7 +67,7 @@ function hook_menu_update($menu) {
 function hook_menu_delete($menu) {
   // Delete the record from our variable.
   $my_menus = variable_get('my_module_menus', array());
-  unset($my_menus[$menu['menu_name']]);
+  unset($my_menus[$menu->id()]);
   variable_set('my_module_menus', $my_menus);
 }
 
diff --git a/core/modules/menu/menu.install b/core/modules/menu/menu.install
index f1a7b05..a76207b 100644
--- a/core/modules/menu/menu.install
+++ b/core/modules/menu/menu.install
@@ -5,63 +5,7 @@
  * Install, update and uninstall functions for the menu module.
  */
 
-/**
- * Implements hook_schema().
- */
-function menu_schema() {
-  $schema['menu_custom'] = array(
-    'description' => 'Holds definitions for top-level custom menus (for example, Main navigation menu).',
-    'fields' => array(
-      'menu_name' => array(
-        'type' => 'varchar',
-        'length' => 32,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => 'Primary Key: Unique key for menu. This is used as a block delta so length is 32.',
-      ),
-      'title' => array(
-        'type' => 'varchar',
-        'length' => 255,
-        'not null' => TRUE,
-        'default' => '',
-        'description' => 'Menu title; displayed at top of block.',
-        'translatable' => TRUE,
-      ),
-      'description' => array(
-        'type' => 'text',
-        'not null' => FALSE,
-        'description' => 'Menu description.',
-        'translatable' => TRUE,
-      ),
-    ),
-    'primary key' => array('menu_name'),
-  );
-
-  return $schema;
-}
-
-/**
- * Implements hook_install().
- */
-function menu_install() {
-  $system_menus = menu_list_system_menus();
-  $t = get_t();
-  $descriptions = array(
-    'tools' => $t('Contains links for site visitors. Some modules add their links here.'),
-    'account' => $t('Links related to the user account.'),
-    'admin' => $t('Contains links to administrative tasks.'),
-    'main' => $t('Use this for linking to the main site sections.'),
-    'footer' => $t('Use this for linking to site information.'),
-  );
-  foreach ($system_menus as $menu_name => $title) {
-    $menu = array(
-      'menu_name' => $menu_name,
-      'title' => $t($title),
-      'description' => $descriptions[$menu_name],
-    );
-    menu_save($menu);
-  }
-}
+use Drupal\Component\Uuid\Uuid;
 
 /**
  * Implements hook_uninstall().
@@ -126,3 +70,22 @@ function menu_update_8003() {
   ));
 }
 
+/**
+ * Migrate menus into configuration.
+ *
+ * @ingroup config_upgrade
+ */
+function menu_update_8004() {
+  $uuid = new Uuid();
+  $result = db_query('SELECT * FROM {menu_custom}');
+  foreach ($result as $menu) {
+    // Save the config object.
+    config('menu.menu.' . $menu->menu_name)
+      ->set('id', $menu->menu_name)
+      ->set('uuid', $uuid->generate())
+      ->set('label', $menu->title)
+      ->set('description', $menu->description)
+      ->save();
+    update_config_manifest_add('menu.menu', array($menu->menu_name));
+  }
+}
diff --git a/core/modules/menu/menu.module b/core/modules/menu/menu.module
index ff24720..3d4a360 100644
--- a/core/modules/menu/menu.module
+++ b/core/modules/menu/menu.module
@@ -12,6 +12,7 @@
  */
 
 use Drupal\node\Plugin\Core\Entity\Node;
+use Drupal\system\Plugin\Core\Entity\Menu;
 use Drupal\system\Plugin\block\block\SystemMenuBlock;
 use Symfony\Component\HttpFoundation\JsonResponse;
 
@@ -82,8 +83,7 @@ function menu_menu() {
   );
   $items['admin/structure/menu/add'] = array(
     'title' => 'Add menu',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('menu_edit_menu', 'add'),
+    'page callback' => 'menu_menu_add',
     'access arguments' => array('administer menu'),
     'type' => MENU_LOCAL_ACTION,
     'file' => 'menu.admin.inc',
@@ -101,7 +101,7 @@ function menu_menu() {
     'title' => 'Customize menu',
     'page callback' => 'drupal_get_form',
     'page arguments' => array('menu_overview_form', 4),
-    'title callback' => 'menu_overview_title',
+    'title callback' => 'entity_page_label',
     'title arguments' => array(4),
     'access arguments' => array('administer menu'),
     'file' => 'menu.admin.inc',
@@ -122,8 +122,8 @@ function menu_menu() {
   );
   $items['admin/structure/menu/manage/%menu/edit'] = array(
     'title' => 'Edit menu',
-    'page callback' => 'drupal_get_form',
-    'page arguments' => array('menu_edit_menu', 'edit', 4),
+    'page callback' => 'menu_menu_edit',
+    'page arguments' => array(4),
     'access arguments' => array('administer menu'),
     'type' => MENU_LOCAL_TASK,
     'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
@@ -161,6 +161,29 @@ function menu_menu() {
 }
 
 /**
+ * Implements hook_entity_info_alter().
+ */
+function menu_entity_info_alter(&$entity_info) {
+  $entity_info['menu']['list_controller_class'] = 'Drupal\menu\MenuListController';
+  $entity_info['menu']['uri_callback'] = 'menu_uri';
+  $entity_info['menu']['form_controller_class'] = array(
+    'default' => 'Drupal\menu\MenuFormController',
+  );
+}
+
+/**
+ * Entity URI callback.
+ *
+ * @param \Drupal\system\Plugin\Core\Entity\Menu $menu
+ *   A Menu entity.
+ */
+function menu_uri(Menu $menu) {
+  return array(
+    'path' => 'admin/structure/menu/manage/' . $menu->id(),
+  );
+}
+
+/**
  * Implements hook_theme().
  */
 function menu_theme() {
@@ -169,10 +192,6 @@ function menu_theme() {
       'file' => 'menu.admin.inc',
       'render element' => 'form',
     ),
-    'menu_admin_overview' => array(
-      'file' => 'menu.admin.inc',
-      'variables' => array('title' => NULL, 'name' => NULL, 'description' => NULL),
-    ),
   );
 }
 
@@ -186,13 +205,13 @@ function menu_enable() {
   $base_link = db_query("SELECT mlid AS plid, menu_name FROM {menu_links} WHERE link_path = 'admin/structure/menu' AND module = 'system'")->fetchAssoc();
   $base_link['router_path'] = 'admin/structure/menu/manage/%';
   $base_link['module'] = 'menu';
-  $result = db_query("SELECT * FROM {menu_custom}", array(), array('fetch' => PDO::FETCH_ASSOC));
-  foreach ($result as $menu) {
+  $menus = entity_load_multiple('menu');
+  foreach ($menus as $menu) {
     // $link is passed by reference to menu_link_save(), so we make a copy of $base_link.
     $link = $base_link;
     $link['mlid'] = 0;
-    $link['link_title'] = $menu['title'];
-    $link['link_path'] = 'admin/structure/menu/manage/' . $menu['menu_name'];
+    $link['link_title'] = $menu->label();
+    $link['link_path'] = 'admin/structure/menu/manage/' . $menu->id();
     $menu_link = db_query("SELECT mlid FROM {menu_links} WHERE link_path = :path AND plid = :plid", array(
       ':path' => $link['link_path'],
       ':plid' => $link['plid']
@@ -206,13 +225,6 @@ function menu_enable() {
 }
 
 /**
- * Title callback for the menu overview page and links.
- */
-function menu_overview_title($menu) {
-  return $menu['title'];
-}
-
-/**
  * Load the data for a single custom menu.
  *
  * @param $menu_name
@@ -221,129 +233,69 @@ function menu_overview_title($menu) {
  *   Array defining the custom menu, or FALSE if the menu doesn't exist.
  */
 function menu_load($menu_name) {
-  $all_menus = menu_load_all();
-  return isset($all_menus[$menu_name]) ? $all_menus[$menu_name] : FALSE;
+  return entity_load('menu', $menu_name);
 }
 
 /**
- * Load all custom menu data.
- *
- * @return
- *   Array of custom menu data.
+ * Implements hook_menu_insert()
  */
-function menu_load_all() {
-  $custom_menus = &drupal_static(__FUNCTION__);
-  if (!isset($custom_menus)) {
-    if ($cached = cache('menu')->get('menu_custom')) {
-      $custom_menus = $cached->data;
-    }
-    else {
-      $custom_menus = db_query('SELECT * FROM {menu_custom}')->fetchAllAssoc('menu_name', PDO::FETCH_ASSOC);
-      cache('menu')->set('menu_custom', $custom_menus);
-    }
+function menu_menu_insert(Menu $menu) {
+  menu_cache_clear_all();
+  // Invalidate the block cache to update menu-based derivatives.
+  if (module_exists('block')) {
+    drupal_container()->get('plugin.manager.block')->clearCachedDefinitions();
+  }
+  // Make sure the menu is present in the active menus variable so that its
+  // items may appear in the menu active trail.
+  // See menu_set_active_menu_names().
+  $config = config('system.menu');
+
+  $active_menus = $config->get('active_menus_default') ?: array_keys(menu_get_menus());
+  if (!in_array($menu->id(), $active_menus)) {
+    $active_menus[] = $menu->id();
+    $config
+      ->set('active_menus_default', $active_menus)
+      ->save();
   }
-  return $custom_menus;
 }
 
 /**
- * Save a custom menu.
- *
- * @param $menu
- *   An array representing a custom menu:
- *   - menu_name: The unique name of the custom menu (composed of lowercase
- *     letters, numbers, and hyphens).
- *   - title: The human readable menu title.
- *   - description: The custom menu description.
- *
- * Modules should always pass a fully populated $menu when saving a custom
- * menu, so other modules are able to output proper status or watchdog messages.
- *
- * @see menu_load()
- */
-function menu_save($menu) {
-  $status = db_merge('menu_custom')
-    ->key(array('menu_name' => $menu['menu_name']))
-    ->fields(array(
-      'title' => $menu['title'],
-      'description' => $menu['description'],
-    ))
-    ->execute();
+ * Implements hook_menu_update().
+ */
+function menu_menu_update(Menu $menu) {
   menu_cache_clear_all();
   // Invalidate the block cache to update menu-based derivatives.
   if (module_exists('block')) {
     drupal_container()->get('plugin.manager.block')->clearCachedDefinitions();
   }
-
-  switch ($status) {
-    case SAVED_NEW:
-      // Make sure the menu is present in the active menus variable so that its
-      // items may appear in the menu active trail.
-      // See menu_set_active_menu_names().
-      $config = config('system.menu');
-
-      $active_menus = $config->get('active_menus_default') ?: array_keys(menu_get_menus());
-      if (!in_array($menu['menu_name'], $active_menus)) {
-        $active_menus[] = $menu['menu_name'];
-        $config->set('active_menus_default', $active_menus);
-      }
-
-      module_invoke_all('menu_insert', $menu);
-      break;
-
-    case SAVED_UPDATED:
-      module_invoke_all('menu_update', $menu);
-      break;
-  }
 }
 
 /**
- * Delete a custom menu and all contained links.
- *
- * Note that this function deletes all menu links in a custom menu. While menu
- * links derived from router paths may be restored by rebuilding the menu, all
- * customized and custom links will be irreversibly gone. Therefore, this
- * function should usually be called from a user interface (form submit) handler
- * only, which allows the user to confirm the action.
- *
- * @param $menu
- *   An array representing a custom menu:
- *   - menu_name: The unique name of the custom menu.
- *   - title: The human readable menu title.
- *   - description: The custom menu description.
- *
- * Modules should always pass a fully populated $menu when deleting a custom
- * menu, so other modules are able to output proper status or watchdog messages.
- *
- * @see menu_load()
- *
- * menu_delete_links() will take care of clearing the page cache. Other modules
- * should take care of their menu-related data by implementing
- * hook_menu_delete().
+ * Implements hook_menu_predelete().
  */
-function menu_delete($menu) {
+function menu_menu_predelete(Menu $menu) {
   // Delete all links from the menu.
-  menu_delete_links($menu['menu_name']);
+  menu_delete_links($menu->id());
 
   // Remove menu from active menus variable.
   $active_menus = variable_get('menu_default_active_menus', array_keys(menu_get_menus()));
   foreach ($active_menus as $i => $menu_name) {
-    if ($menu['menu_name'] == $menu_name) {
+    if ($menu->id() == $menu_name) {
       unset($active_menus[$i]);
       variable_set('menu_default_active_menus', $active_menus);
     }
   }
+}
 
-  // Delete the custom menu.
-  db_delete('menu_custom')
-    ->condition('menu_name', $menu['menu_name'])
-    ->execute();
-
+/**
+ * Implements hook_menu_delete().
+ */
+function menu_menu_delete(Menu $menu) {
   menu_cache_clear_all();
   // Invalidate the block cache to update menu-based derivatives.
   if (module_exists('block')) {
     drupal_container()->get('plugin.manager.block')->clearCachedDefinitions();
   }
-  module_invoke_all('menu_delete', $menu);
 }
 
 /**
@@ -778,12 +730,12 @@ function menu_form_node_type_form_alter(&$form, $form_state) {
  *   titles as the values.
  */
 function menu_get_menus($all = TRUE) {
-  if ($custom_menus = menu_load_all()) {
+  if ($custom_menus = entity_load_multiple('menu')) {
     if (!$all) {
       $custom_menus = array_diff_key($custom_menus, menu_list_system_menus());
     }
     foreach ($custom_menus as $menu_name => $menu) {
-      $custom_menus[$menu_name] = t($menu['title']);
+      $custom_menus[$menu_name] = $menu->label();
     }
     asort($custom_menus);
   }
diff --git a/core/modules/system/lib/Drupal/system/Plugin/Core/Entity/Menu.php b/core/modules/system/lib/Drupal/system/Plugin/Core/Entity/Menu.php
new file mode 100644
index 0000000..7e3ceb0
--- /dev/null
+++ b/core/modules/system/lib/Drupal/system/Plugin/Core/Entity/Menu.php
@@ -0,0 +1,60 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Plugin\Core\Entity\Menu.
+ */
+
+namespace Drupal\system\Plugin\Core\Entity;
+
+use Drupal\Core\Config\Entity\ConfigEntityBase;
+use Drupal\Core\Annotation\Plugin;
+use Drupal\Core\Annotation\Translation;
+
+/**
+ * Defines the Menu configuration entity class.
+ *
+ * @Plugin(
+ *   id = "menu",
+ *   label = @Translation("Menu"),
+ *   module = "system",
+ *   controller_class = "Drupal\Core\Config\Entity\ConfigStorageController",
+ *   config_prefix = "menu.menu",
+ *   entity_keys = {
+ *     "id" = "id",
+ *     "label" = "label",
+ *     "uuid" = "uuid"
+ *   }
+ * )
+ */
+class Menu extends ConfigEntityBase {
+
+  /**
+   * The menu machine name.
+   *
+   * @var string
+   */
+  public $id;
+
+  /**
+   * The menu UUID.
+   *
+   * @var string
+   */
+  public $uuid;
+
+  /**
+   * The human-readable name of the menu entity.
+   *
+   * @var string
+   */
+  public $label;
+
+  /**
+   * The menu description.
+   *
+   * @var string
+   */
+  public $description;
+
+}
