diff --git a/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php b/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php
index 33ae97b..29a2f83 100644
--- a/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php
+++ b/core/lib/Drupal/Core/Menu/DefaultMenuLinkTreeManipulators.php
@@ -68,12 +68,13 @@ public function checkAccess(array $tree) {
     foreach ($tree as $key => $element) {
       // Other menu tree manipulators may already have calculated access, do not
       // overwrite the existing value in that case.
-      if (!isset($element->access)) {
-        $tree[$key]->access = $this->menuLinkCheckAccess($element->link);
+      if ($element->isAccessible() === NULL) {
+        $tree[$key]->setAccessible($this->menuLinkCheckAccess($element->getLink()));
       }
-      if ($tree[$key]->access) {
-        if ($tree[$key]->subtree) {
-          $tree[$key]->subtree = $this->checkAccess($tree[$key]->subtree);
+      if ($tree[$key]->isAccessible()) {
+        $subtree = $tree[$key]->getSubtree();
+        if (!empty($subtree)) {
+          $tree[$key]->setSubtree($this->checkAccess($subtree));
         }
       }
       else {
@@ -118,15 +119,16 @@ protected function menuLinkCheckAccess(MenuLinkInterface $instance) {
   public function generateIndexAndSort(array $tree) {
     $new_tree = array();
     foreach ($tree as $key => $v) {
-      if ($tree[$key]->subtree) {
-        $tree[$key]->subtree = $this->generateIndexAndSort($tree[$key]->subtree);
+      $subtree = $tree[$key]->getSubtree();
+      if ($subtree) {
+        $tree[$key]->setSubtree($this->generateIndexAndSort($subtree));
       }
-      $instance = $tree[$key]->link;
+      $link = $tree[$key]->getLink();
       // The weights are made a uniform 5 digits by adding 50000 as an offset.
       // After $this->menuLinkCheckAccess(), $instance->getTitle() has the
       // localized or translated title. Adding the plugin id to the end of the
       // index insures that it is unique.
-      $new_tree[(50000 + $instance->getWeight()) . ' ' . $instance->getTitle() . ' ' . $instance->getPluginId()] = $tree[$key];
+      $new_tree[(50000 + $link->getWeight()) . ' ' . $link->getTitle() . ' ' . $link->getPluginId()] = $tree[$key];
     }
     ksort($new_tree);
     return $new_tree;
@@ -143,10 +145,11 @@ public function generateIndexAndSort(array $tree) {
    */
   public function flatten(array $tree) {
     foreach ($tree as $key => $element) {
-      if ($tree[$key]->subtree) {
-        $tree += $this->flatten($tree[$key]->subtree);
+      $subtree = $tree[$key]->getSubtree();
+      if ($subtree) {
+        $tree += $this->flatten($subtree);
       }
-      $tree[$key]->subtree = array();
+      $tree[$key]->setSubtree(array());
     }
     return $tree;
   }
@@ -168,9 +171,9 @@ public function extractSubtreeOfActiveTrail(array $tree, $level) {
       // Loop through the current level's elements  until we find one that is in
       // the active trail.
       while ($element = array_shift($tree)) {
-        if ($element->inActiveTrail) {
+        if ($element->isInActiveTrail()) {
           // If the element is in the active trail, we continue in the subtree.
-          $tree = $element->subtree;
+          $tree = $element->getSubtree();
           break;
         }
       }
diff --git a/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php b/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php
new file mode 100644
index 0000000..1179bdc
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php
@@ -0,0 +1,206 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\Form\MenuLinkDefaultForm.
+ */
+
+namespace Drupal\Core\Menu\Form;
+
+use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
+use Drupal\Core\Menu\MenuLinkInterface;
+use Drupal\Core\Menu\MenuLinkManagerInterface;
+use Drupal\Core\Menu\MenuParentFormSelectorInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+use Drupal\Core\StringTranslation\TranslationInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Provides an edit form for static menu links.
+ *
+ * @see \Drupal\Core\Menu\MenuLinkDefault
+ */
+class MenuLinkDefaultForm implements MenuLinkFormInterface, ContainerInjectionInterface {
+
+  use StringTranslationTrait;
+
+  /**
+   * The edited menu link.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkInterface
+   */
+  protected $menuLink;
+
+  /**
+   * The menu link manager.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkManagerInterface
+   */
+  protected $menuLinkManager;
+
+  /**
+   * The menu tree service.
+   *
+   * @var \Drupal\Core\Menu\MenuParentFormSelectorInterface
+   */
+  protected $menuParentSelector;
+
+  /**
+   * The module handler service.
+   *
+   * @var \Drupal\Core\Extension\ModuleHandlerInterface
+   */
+  protected $moduleHandler;
+
+  /**
+   * The module data from system_get_info().
+   *
+   * @var array
+   */
+  protected $moduleData;
+
+  /**
+   * Constructs a new MenuLinkDefaultForm.
+   *
+   * @param \Drupal\Core\Menu\MenuLinkManagerInterface $menu_link_manager
+   *   The menu link manager.
+   * @param \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_selector
+   *   The menu parent form selector service.
+   * @param \Drupal\Core\StringTranslation\TranslationInterface $string_translation
+   *   The string translation.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler;
+   */
+  public function __construct(MenuLinkManagerInterface $menu_link_manager, MenuParentFormSelectorInterface $menu_parent_selector, TranslationInterface $string_translation, ModuleHandlerInterface $module_handler) {
+    $this->menuLinkManager = $menu_link_manager;
+    $this->menuParentSelector = $menu_parent_selector;
+    $this->stringTranslation = $string_translation;
+    $this->moduleHandler = $module_handler;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('plugin.manager.menu.link'),
+      $container->get('menu.parent_form_selector'),
+      $container->get('string_translation'),
+      $container->get('module_handler')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setMenuLinkInstance(MenuLinkInterface $menu_link) {
+    $this->menuLink = $menu_link;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, array &$form_state) {
+    $form['#title'] = $this->t('Edit menu link %title', array('%title' => $this->menuLink->getTitle()));
+
+    $provider = $this->menuLink->getProvider();
+    $form['info'] = array(
+      '#type' => 'item',
+      '#title' => $this->t('This link is provided by the @name module. The label and path cannot be edited.', array('@name' => $this->getModuleName($provider))),
+    );
+    $link = array(
+      '#type' => 'link',
+      '#title' => $this->menuLink->getTitle(),
+    ) + $this->menuLink->getUrlObject()->toRenderArray();
+    $form['path'] = array(
+      'link' => $link,
+      '#type' => 'item',
+      '#title' => $this->t('Link'),
+    );
+
+    $form['enabled'] = array(
+      '#type' => 'checkbox',
+      '#title' => $this->t('Enable'),
+      '#description' => $this->t('Menu links that are not enabled will not be listed in any menu.'),
+      '#default_value' => !$this->menuLink->isHidden(),
+    );
+    $form['expanded'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Show as expanded'),
+      '#description' => $this->t('If selected and this menu link has children, the menu will always appear expanded.'),
+      '#default_value' => $this->menuLink->isExpanded(),
+    );
+    $delta = max(abs($this->menuLink->getWeight()), 50);
+    $form['weight'] = array(
+      '#type' => 'number',
+      '#min' => -$delta,
+      '#max' => $delta,
+      '#default_value' => $this->menuLink->getWeight(),
+      '#title' => $this->t('Weight'),
+      '#description' => $this->t('Optional. In the menu, the links with high weight will sink and links with a low weight will be positioned nearer the top.'),
+    );
+
+    $menu_parent = $this->menuLink->getMenuName() . ':' . $this->menuLink->getParent();
+    $form['menu_parent'] = $this->menuParentSelector->parentSelectElement($menu_parent, $this->menuLink->getPluginId());
+    $form['menu_parent']['#title'] = $this->t('Parent link');
+    $form['menu_parent']['#description'] = $this->t('The maximum depth for a link and all its children is fixed. Some menu links may not be available as parents if selecting them would exceed this limit.');
+    $form['menu_parent']['#attributes']['class'][] = 'menu-title-select';
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function extractFormValues(array &$form, array &$form_state) {
+    $new_definition = array();
+    $new_definition['hidden'] = $form_state['values']['enabled'] ? 0 : 1;
+    $new_definition['weight'] = (int) $form_state['values']['weight'];
+    $new_definition['expanded'] = $form_state['values']['expanded'] ? 1 : 0;
+    list($menu_name, $parent) = explode(':', $form_state['values']['menu_parent'], 2);
+    if (!empty($menu_name)) {
+      $new_definition['menu_name'] = $menu_name;
+    }
+    if (isset($parent)) {
+      $new_definition['parent'] = $parent;
+    }
+    return $new_definition;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, array &$form_state) {
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitConfigurationForm(array &$form, array &$form_state) {
+    $new_definition = $this->extractFormValues($form, $form_state);
+
+    return $this->menuLinkManager->updateDefinition($this->menuLink->getPluginId(), $new_definition);
+  }
+
+  /**
+   * Helper function to get a module name.
+   *
+   * This function is horrible, but core has nothing better until we add a
+   * a method to the ModuleHandler that handles this nicely.
+   * @see https://drupal.org/node/2281989
+   */
+  protected function getModuleName($module) {
+    // Gather module data.
+    if (!isset($this->moduleData)) {
+      $this->moduleData = system_get_info('module');
+    }
+    // If the module exists, return its human-readable name.
+    if (isset($this->moduleData[$module])) {
+      return $this->t($this->moduleData[$module]['name']);
+    }
+    // Otherwise, return the machine name.
+    return $module;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Menu/Form/MenuLinkFormInterface.php b/core/lib/Drupal/Core/Menu/Form/MenuLinkFormInterface.php
new file mode 100644
index 0000000..cdc0f85
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/Form/MenuLinkFormInterface.php
@@ -0,0 +1,44 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Menu\Form\MenuLinkFormInterface.
+ */
+
+namespace Drupal\Core\Menu\Form;
+
+use Drupal\Core\Menu\MenuLinkInterface;
+use Drupal\Core\Plugin\PluginFormInterface;
+
+/**
+ * Defines an interface for edit forms of menu links.
+ *
+ * All menu link plugins use the same interface for their configuration or
+ * editing form, but the implementations may differ.
+ *
+ * @see \Drupal\Core\Menu\MenuLinkInterface::getFormClass()
+ */
+interface MenuLinkFormInterface extends PluginFormInterface {
+
+  /**
+   * Injects the menu link instance.
+   *
+   * @param \Drupal\Core\Menu\MenuLinkInterface $menu_link
+   *   A menu link plugin instance.
+   */
+  public function setMenuLinkInstance(MenuLinkInterface $menu_link);
+
+  /**
+   * Form plugin helper.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param array $form_state
+   *   An associative array containing the current state of the form.
+   *
+   * @return array
+   *   The new plugin definition values takes from the form values.
+   */
+  public function extractFormValues(array &$form, array &$form_state);
+
+}
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkTree.php b/core/lib/Drupal/Core/Menu/MenuLinkTree.php
index 8d09c2c..931e4d5 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkTree.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkTree.php
@@ -152,27 +152,29 @@ public function transform(array $tree, array $manipulators) {
   public function build(array $tree) {
     $build = array();
 
+    /** @var \Drupal\Core\Menu\MenuLinkTreeElement $data */
     foreach ($tree as $data) {
       $class = array();
       /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
-      $link = $data->link;
+      $link = $data->getLink();
       // Generally we only deal with visible links, but just in case.
       if ($link->isHidden()) {
         continue;
       }
       // Set a class for the <li>-tag. Only set 'expanded' class if the link
       // also has visible children within the current tree.
-      if ($data->hasChildren && !empty($data->subtree)) {
+      $subtree = $data->getSubtree();
+      if ($data->hasChildren() && !empty($subtree)) {
         $class[] = 'expanded';
       }
-      elseif ($data->hasChildren) {
+      elseif ($data->hasChildren()) {
         $class[] = 'collapsed';
       }
       else {
         $class[] = 'leaf';
       }
       // Set a class if the link is in the active trail.
-      if ($data->inActiveTrail) {
+      if ($data->isInActiveTrail()) {
         $class[] = 'active-trail';
       }
 
@@ -181,9 +183,9 @@ public function build(array $tree) {
       $element['#attributes']['class'] = $class;
       $element['#title'] = $link->getTitle();
       $element['#url'] = $link->getUrlObject();
-      $element['#below'] = $data->subtree ? $this->build($data->subtree) : array();
-      if (isset($data->options)) {
-        $element['#url']->setOptions(NestedArray::mergeDeep($element['#url']->getOptions(), $data->options));
+      $element['#below'] = $subtree ? $this->build($subtree) : array();
+      if (count($data->getOptions())) {
+        $element['#url']->setOptions(NestedArray::mergeDeep($element['#url']->getOptions(), $data->getOptions()));
       }
       $element['#original_link'] = $link;
       // Index using the link's unique ID.
diff --git a/core/lib/Drupal/Core/Menu/MenuLinkTreeElement.php b/core/lib/Drupal/Core/Menu/MenuLinkTreeElement.php
index ed79fd9..26db54f 100644
--- a/core/lib/Drupal/Core/Menu/MenuLinkTreeElement.php
+++ b/core/lib/Drupal/Core/Menu/MenuLinkTreeElement.php
@@ -34,7 +34,7 @@ class MenuLinkTreeElement {
    *
    * @var \Drupal\Core\Menu\MenuLinkInterface
    */
-  public $link;
+  protected $link;
 
   /**
    * The subtree of this element in the menu link tree (this link's children).
@@ -44,38 +44,35 @@ class MenuLinkTreeElement {
    *
    * @var \Drupal\Core\Menu\MenuLinkTreeElement[]
    */
-  public $subtree;
+  protected $subtree;
 
   /**
    * The depth of this link relative to the root of the tree.
    *
    * @var int
    */
-  public $depth;
+  protected $depth;
 
   /**
    * Whether this link has any children at all.
    *
    * @var bool
    */
-  public $hasChildren;
+  protected $hasChildren;
 
   /**
    * Whether this link is in the active trail.
    *
    * @var bool
    */
-  public $inActiveTrail;
+  protected $inActiveTrail;
 
   /**
    * Whether this link is accessible by the current user.
    *
-   * If the value is NULL the access was not determined yet, if Boolean it was
-   * determined already.
-   *
    * @var bool|NULL
    */
-  public $access;
+  protected $access;
 
   /**
    * Additional options for this link.
@@ -84,7 +81,7 @@ class MenuLinkTreeElement {
    * \Drupal\Core\Menu\MenuLinkInterface::getOptions(), to allow menu link tree
    * manipulators to add or override link options.
    */
-  public $options = array();
+  protected $options = array();
 
   /**
    * Constructs a new \Drupal\Core\Menu\MenuLinkTreeElement.
@@ -122,7 +119,96 @@ public function count() {
     $sum = function ($carry, MenuLinkTreeElement $element) {
       return $carry + $element->count();
     };
-    return 1 + array_reduce($this->subtree, $sum);
+    return 1 + array_reduce($this->getSubtree(), $sum);
+  }
+
+  /**
+   * Gets the menu link for this element in the menu link tree.
+   *
+   * @return \Drupal\Core\Menu\MenuLinkInterface
+   */
+  public function getLink() {
+    return $this->link;
+  }
+
+  /**
+   * Gets the subtree of this element in the menu link tree.
+   *
+   * @return \Drupal\Core\Menu\MenuLinkTreeElement[]
+   */
+  public function getSubtree() {
+    return $this->subtree;
+  }
+
+  /**
+   * Sets the subtree of this element in the menu link tree.
+   *
+   * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $subtree
+   *   The subtree to set.
+   *
+   * @return $this
+   */
+  public function setSubtree(array $subtree) {
+    $this->subtree = $subtree;
+    return $this;
+  }
+
+  /**
+   * Gets the depth of this link relative to the root of the tree.
+   *
+   * @return int
+   */
+  public function getDepth() {
+    return $this->depth;
+  }
+
+  /**
+   * Indicates whether this link has any children at all.
+   *
+   * @return bool
+   */
+  public function hasChildren() {
+    return $this->hasChildren;
+  }
+
+  /**
+   * Indicates whether this link is in the active trail.
+   *
+   * @return bool
+   */
+  public function isInActiveTrail() {
+    return $this->inActiveTrail;
+  }
+
+  /**
+   * Indicates whether this link is accessible by the current user.
+   *
+   * @return bool|null
+   *   If the value is NULL the access was not determined yet, if Boolean it was
+   *   determined already.
+   */
+  public function isAccessible() {
+    return $this->access;
+  }
+
+  /**
+   * Whether this link is accessible by the current user.
+   *
+   * @param bool $access
+   * @return $this
+   */
+  public function setAccessible($access) {
+    $this->access = $access;
+    return $this;
+  }
+
+  /**
+   * Get additional options for this link.
+   *
+   * @return array
+   */
+  public function getOptions() {
+    return $this->options;
   }
 
 }
diff --git a/core/lib/Drupal/Core/Menu/MenuParentFormSelector.php b/core/lib/Drupal/Core/Menu/MenuParentFormSelector.php
index 4164379..6aedc57 100644
--- a/core/lib/Drupal/Core/Menu/MenuParentFormSelector.php
+++ b/core/lib/Drupal/Core/Menu/MenuParentFormSelector.php
@@ -136,19 +136,20 @@ protected function getParentDepthLimit($id) {
    */
   protected function parentSelectOptionsTreeWalk(array $tree, $menu_name, $indent, array &$options, $exclude, $depth_limit) {
     foreach ($tree as $element) {
-      if ($element->depth > $depth_limit) {
+      if ($element->getDepth() > $depth_limit) {
         // Don't iterate through any links on this level.
         break;
       }
-      $link = $element->link;
+      $link = $element->getLink();
       if ($link->getPluginId() != $exclude) {
         $title = $indent . ' ' . Unicode::truncate($link->getTitle(), 30, TRUE, FALSE);
         if ($link->isHidden()) {
           $title .= ' (' . $this->t('disabled') . ')';
         }
         $options[$menu_name . ':' . $link->getPluginId()] = $title;
-        if (!empty($element->subtree)) {
-          $this->parentSelectOptionsTreeWalk($element->subtree, $menu_name, $indent . '--', $options, $exclude, $depth_limit);
+        $subtree = $element->getSubtree();
+        if (!empty($subtree)) {
+          $this->parentSelectOptionsTreeWalk($subtree, $menu_name, $indent . '--', $options, $exclude, $depth_limit);
         }
       }
     }
diff --git a/core/modules/menu_link_content/menu_link_content.local_tasks.yml b/core/modules/menu_link_content/menu_link_content.local_tasks.yml
new file mode 100644
index 0000000..8195236
--- /dev/null
+++ b/core/modules/menu_link_content/menu_link_content.local_tasks.yml
@@ -0,0 +1,4 @@
+menu_link_content.link_edit:
+  route_name: menu_link_content.link_edit
+  base_route: menu_link_content.link_edit
+  title: Edit
diff --git a/core/modules/menu_link_content/menu_link_content.routing.yml b/core/modules/menu_link_content/menu_link_content.routing.yml
new file mode 100644
index 0000000..f96b22d
--- /dev/null
+++ b/core/modules/menu_link_content/menu_link_content.routing.yml
@@ -0,0 +1,23 @@
+menu_link_content.link_add:
+  path: '/admin/structure/menu/manage/{menu}/add'
+  defaults:
+    _content: '\Drupal\menu_link_content\Controller\MenuController::addLink'
+    _title: 'Add menu link'
+  requirements:
+    _entity_create_access: 'menu_link_content'
+
+menu_link_content.link_edit:
+  path: '/admin/structure/menu/item/{menu_link_content}/edit'
+  defaults:
+    _entity_form: 'menu_link_content.default'
+    _title: 'Edit menu link'
+  requirements:
+    _entity_access: 'menu_link_content.update'
+
+menu_link_content.link_delete:
+  path: '/admin/structure/menu/item/{menu_link_content}/delete'
+  defaults:
+    _entity_form: 'menu_link_content.delete'
+    _title: 'Delete menu link'
+  requirements:
+    _entity_access: 'menu_link_content.delete'
diff --git a/core/modules/menu_link_content/src/Controller/MenuController.php b/core/modules/menu_link_content/src/Controller/MenuController.php
new file mode 100644
index 0000000..7ffa9f8
--- /dev/null
+++ b/core/modules/menu_link_content/src/Controller/MenuController.php
@@ -0,0 +1,34 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Controller\MenuController.
+ */
+
+namespace Drupal\menu_link_content\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\system\MenuInterface;
+
+class MenuController extends ControllerBase {
+
+  /**
+   * Provides the menu link submission form.
+   *
+   * @param \Drupal\system\MenuInterface $menu
+   *   An entity representing a custom menu.
+   *
+   * @return array
+   *   Returns the menu link submission form.
+   */
+  public function addLink(MenuInterface $menu) {
+    $menu_link = $this->entityManager()->getStorage('menu_link_content')->create(array(
+      'id' => '',
+      'parent' => '',
+      'menu_name' => $menu->id(),
+      'bundle' => 'menu_link_content',
+    ));
+    return $this->entityFormBuilder()->getForm($menu_link);
+  }
+
+}
diff --git a/core/modules/menu_link_content/src/Form/MenuLinkContentDeleteForm.php b/core/modules/menu_link_content/src/Form/MenuLinkContentDeleteForm.php
new file mode 100644
index 0000000..4be850a
--- /dev/null
+++ b/core/modules/menu_link_content/src/Form/MenuLinkContentDeleteForm.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Form\MenuLinkContentDeleteForm.
+ */
+
+namespace Drupal\menu_link_content\Form;
+
+use Drupal\Core\Entity\ContentEntityConfirmFormBase;
+use Drupal\Core\Url;
+
+/**
+ * Provides a delete form for content menu links.
+ */
+class MenuLinkContentDeleteForm extends ContentEntityConfirmFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getQuestion() {
+    return $this->t('Are you sure you want to delete the custom menu link %item?', array('%item' => $this->entity->getTitle()));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getCancelRoute() {
+    return new Url('menu_ui.menu_edit', array('menu' => $this->entity->getMenuName()));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submit(array $form, array &$form_state) {
+    $storage = $this->entityManager->getStorage('menu_link_content');
+    $storage->delete(array($this->entity));
+    $t_args = array('%title' => $this->entity->getTitle());
+    drupal_set_message($this->t('The menu link %title has been deleted.', $t_args));
+    watchdog('menu', 'Deleted menu link %title.', $t_args, WATCHDOG_NOTICE);
+    $form_state['redirect_route'] = array(
+      'route_name' => '<front>',
+    );
+  }
+
+}
diff --git a/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
new file mode 100644
index 0000000..04cfac1
--- /dev/null
+++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
@@ -0,0 +1,410 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Form\MenuLinkContentForm.
+ */
+
+namespace Drupal\menu_link_content\Form;
+
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Url;
+use Drupal\Core\Entity\ContentEntityForm;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\Core\Menu\Form\MenuLinkFormInterface;
+use Drupal\Core\Menu\MenuLinkInterface;
+use Drupal\Core\Menu\MenuParentFormSelectorInterface;
+use Drupal\Core\Path\AliasManagerInterface;
+use Drupal\Core\Language\Language;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\Core\Routing\MatchingRouteNotFoundException;
+use Drupal\Core\ParamConverter\ParamNotConvertedException;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\Routing\RequestContext;
+
+/**
+ * Provides a form to add/update content menu links.
+ *
+ * Note: This is not only a content entity form, but also implements the
+ * MenuLinkFormInterface, so the same class can be used in places expecting a
+ * generic menu link plugin configuration form.
+ */
+class MenuLinkContentForm extends ContentEntityForm implements MenuLinkFormInterface {
+
+  /**
+   * The content menu link.
+   *
+   * @var \Drupal\menu_link_content\Entity\MenuLinkContent
+   */
+  protected $entity;
+
+  /**
+   * The menu tree service.
+   *
+   * @var \Drupal\Core\Menu\MenuParentFormSelectorInterface
+   */
+  protected $menuParentSelector;
+
+  /**
+   * The request context.
+   *
+   * @var \Symfony\Component\Routing\RequestContext
+   */
+  protected $requestContext;
+
+  /**
+   * The language manager.
+   *
+   * @var \Drupal\Core\Language\LanguageManagerInterface
+   */
+  protected $languageManager;
+
+  /**
+   * Constructs a MenuLinkContentForm object.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\Menu\MenuParentFormSelectorInterface $menu_parent_selector
+   *   The menu parent form selector service.
+   * @param \Drupal\Core\Path\AliasManagerInterface $alias_manager
+   *   The alias manager.
+   * @param \Drupal\Core\Extension\ModuleHandlerInterface $module_handler
+   *   The module handler;
+   * @param \Symfony\Component\Routing\RequestContext $request_context
+   *   The request context.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   */
+  public function __construct(EntityManagerInterface $entity_manager, MenuParentFormSelectorInterface $menu_parent_selector, AliasManagerInterface $alias_manager, ModuleHandlerInterface $module_handler, RequestContext $request_context, LanguageManagerInterface $language_manager) {
+    parent::__construct($entity_manager, $language_manager);
+    $this->menuParentSelector = $menu_parent_selector;
+    $this->pathAliasManager = $alias_manager;
+    $this->moduleHandler = $module_handler;
+    $this->requestContext = $request_context;
+    $this->languageManager = $language_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity.manager'),
+      $container->get('menu.parent_form_selector'),
+      $container->get('path.alias_manager'),
+      $container->get('module_handler'),
+      $container->get('router.request_context'),
+      $container->get('language_manager')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setMenuLinkInstance(MenuLinkInterface $menu_link) {
+    // Load the entity for the entity form.
+    $metadata = $menu_link->getMetaData();
+    if (!empty($metadata['entity_id'])) {
+      $this->entity = $this->entityManager->getStorage('menu_link_content')->load($metadata['entity_id']);
+    }
+    else {
+      // Fallback to the loading by the uuid.
+      $links = $this->entityManager->getStorage('menu_link_content')->loadByProperties(array('uuid' => $menu_link->getDerivativeId()));
+      $this->entity = reset($links);
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildConfigurationForm(array $form, array &$form_state) {
+    return $this->buildEditForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildEditForm(array &$form, array &$form_state) {
+    $this->setOperation('default');
+    $this->init($form_state);
+
+    return $this->form($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateConfigurationForm(array &$form, array &$form_state) {
+    $this->doValidate($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateEditForm(array &$form, array &$form_state) {
+    $this->doValidate($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitConfigurationForm(array &$form, array &$form_state) {
+    return $this->submitEditForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitEditForm(array &$form, array &$form_state) {
+    // Remove button and internal Form API values from submitted values.
+    form_state_values_clean($form_state);
+    $this->entity = $this->buildEntity($form, $form_state);
+    $this->entity->save();
+    return $form_state;
+  }
+
+  /**
+   * Break up a user-entered URL or path into all the relevant parts.
+   *
+   * @param string $url
+   *   The user-entered URL or path.
+   *
+   * @return array
+   *   The extracted parts.
+   */
+  protected function extractUrl($url) {
+    $extracted = UrlHelper::parse($url);
+    $external = UrlHelper::isExternal($url);
+    if ($external) {
+      $extracted['url'] = $extracted['path'];
+      $extracted['route_name'] = NULL;
+      $extracted['route_parameters'] = array();
+    }
+    else {
+      $extracted['url'] = '';
+      // If the path doesn't match a Drupal path, the route should end up empty.
+      $extracted['route_name'] = NULL;
+      $extracted['route_parameters'] = array();
+      try {
+        // Find the route_name.
+        $normal_path = $this->pathAliasManager->getPathByAlias($extracted['path']);
+        $url_obj = Url::createFromPath($normal_path);
+        $extracted['route_name'] = $url_obj->getRouteName();
+        $extracted['route_parameters'] = $url_obj->getRouteParameters();
+      }
+      catch (MatchingRouteNotFoundException $e) {
+        // The path doesn't match a Drupal path.
+      }
+      catch (ParamNotConvertedException $e) {
+        // A path like node/99 matched a route, but the route parameter was
+        // invalid (e.g. node with ID 99 does not exist).
+      }
+    }
+    return $extracted;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function extractFormValues(array &$form, array &$form_state) {
+
+    $new_definition = array();
+    $new_definition['expanded'] = !empty($form_state['values']['expanded']) ? 1 : 0;
+    $new_definition['hidden'] = empty($form_state['values']['enabled']) ? 1 : 0;
+    list($menu_name, $parent) = explode(':', $form_state['values']['menu_parent'], 2);
+    if (!empty($menu_name)) {
+      $new_definition['menu_name'] = $menu_name;
+    }
+    $new_definition['parent'] = isset($parent) ? $parent : '';
+
+    $extracted = $this->extractUrl($form_state['values']['url']);
+    $new_definition['url'] = $extracted['url'];
+    $new_definition['route_name'] = $extracted['route_name'];
+    $new_definition['route_parameters'] = $extracted['route_parameters'];
+    $new_definition['options'] = array();
+    if ($extracted['query']) {
+      $new_definition['options']['query'] = $extracted['query'];
+    }
+    if ($extracted['fragment']) {
+      $new_definition['options']['fragment'] = $extracted['fragment'];
+    }
+    $new_definition['title'] = $form_state['values']['title'][0]['value'];
+    $new_definition['description'] = $form_state['values']['description'][0]['value'];
+    $new_definition['weight'] = (int) $form_state['values']['weight'][0]['value'];
+
+    return $new_definition;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, array &$form_state) {
+
+    $form = parent::form($form, $form_state);
+
+    $language_configuration = $this->moduleHandler->invoke('language', 'get_default_configuration', array('menu_link_content', 'menu_link_content'));
+    if ($this->entity->isNew()) {
+      $default_language = isset($language_configuration['langcode']) ? $language_configuration['langcode'] : $this->languageManager->getDefaultLanguage()->id;
+    }
+    else {
+      $default_language = $this->entity->getUntranslated()->language()->id;
+    }
+    $form['langcode'] = array(
+      '#title' => t('Language'),
+      '#type' => 'language_select',
+      '#default_value' => $default_language,
+      '#languages' => Language::STATE_ALL,
+      '#access' => !empty($language_configuration['language_show']),
+    );
+
+    $form['enabled'] = array(
+      '#type' => 'checkbox',
+      '#title' => $this->t('Enable menu link'),
+      '#description' => $this->t('Menu links that are not enabled will not be listed in any menu.'),
+      '#default_value' => !$this->entity->isHidden(),
+    );
+
+    // @TODO For whatever reason the expanded widget is not autogenerated.
+    $form['expanded'] = array(
+      '#type' => 'checkbox',
+      '#title' => $this->t('Show as expanded'),
+      '#description' => $this->t('If selected and this menu link has children, the menu will always appear expanded.'),
+      '#default_value' => $this->entity->isExpanded(),
+    );
+
+    // We always show the internal path here.
+    $url = $this->getEntity()->getUrlObject();
+    if ($url->isExternal()) {
+      $default_value = $url->toString();
+    }
+    elseif ($url->getRouteName() == '<front>') {
+      // The default route for new entities is <front>, but we just want an
+      // empty form field.
+      $default_value = $this->getEntity()->isNew() ? '' : '<front>';
+    }
+    else {
+      // @TODO Maybe support options in
+      // \Drupal\Core\Routing\UrlGeneratorInterface::getInternalPath().
+      // or a helper method to render just options?
+      $default_value = $url->getInternalPath();
+      $options = $url->getOptions();
+      if (isset($options['query'])) {
+        $default_value .= $options['query'] ? ('?' . UrlHelper::buildQuery($options['query'])) : '';
+      }
+      if (isset($options['fragment']) && $options['fragment'] !== '') {
+        $default_value .= '#' . $options['fragment'];
+      }
+    }
+    $form['url'] = array(
+      '#title' => $this->t('Link path'),
+      '#type' => 'textfield',
+      '#description' => $this->t('The path for this menu link. This can be an internal Drupal path such as %add-node or an external URL such as %drupal. Enter %front to link to the front page.', array('%front' => '<front>', '%add-node' => 'node/add', '%drupal' => 'http://drupal.org')),
+      '#default_value' => $default_value,
+      '#required' => TRUE,
+    );
+    $default = $this->entity->getMenuName() . ':' . $this->entity->getParentId();
+    $form['menu_parent'] = $this->menuParentSelector->parentSelectElement($default, $this->entity->getPluginId());
+    $form['menu_parent']['#title'] = $this->t('Parent link');
+    $form['menu_parent']['#description'] = $this->t('The maximum depth for a link and all its children is fixed. Some menu links may not be available as parents if selecting them would exceed this limit.');
+    $form['menu_parent']['#attributes']['class'][] = 'menu-title-select';
+
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function actions(array $form, array &$form_state) {
+    $element = parent::actions($form, $form_state);
+    $element['submit']['#button_type'] = 'primary';
+    $element['delete']['#access'] = $this->entity->access('delete');
+
+    return $element;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validate(array $form, array &$form_state) {
+    $this->doValidate($form, $form_state);
+
+    parent::validate($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildEntity(array $form, array &$form_state) {
+    /** @var \Drupal\menu_link_content\Entity\MenuLinkContent $entity */
+    $entity = parent::buildEntity($form, $form_state);
+    $new_definition = $this->extractFormValues($form, $form_state);
+
+    $entity->parent->value = $new_definition['parent'];
+    $entity->menu_name->value = $new_definition['menu_name'];
+    $entity->hidden->value = (bool) $new_definition['hidden'];
+    $entity->expanded->value = $new_definition['expanded'];
+
+    $entity->url->value = $new_definition['url'];
+    $entity->route_name->value = $new_definition['route_name'];
+    $entity->setRouteParameters($new_definition['route_parameters']);
+    $entity->setOptions($new_definition['options']);
+
+    return $entity;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function save(array $form, array &$form_state) {
+    // The entity is rebuilt in parent::submit().
+    $menu_link = $this->entity;
+    $saved = $menu_link->save();
+
+    if ($saved) {
+      drupal_set_message(t('The menu link has been saved.'));
+      $form_state['redirect_route'] = array(
+        'route_name' => 'menu_link_content.link_edit',
+        'route_parameters' => array(
+          'menu_link_content' => $menu_link->id(),
+        ),
+      );
+    }
+    else {
+      drupal_set_message(t('There was an error saving the menu link.'), 'error');
+      $form_state['rebuild'] = TRUE;
+    }
+  }
+
+  /**
+   * Validates the form, both on the menu link edit and content menu link form.
+   */
+  protected function doValidate(array $form, array &$form_state) {
+    $extracted = $this->extractUrl($form_state['values']['url']);
+
+    // If both url and route_nae are empty, the entered value is not valid.
+    $valid = FALSE;
+    if ($extracted['url']) {
+      // This is an external link.
+      $valid = TRUE;
+    }
+    elseif ($extracted['route_name']) {
+      // Users are not allowed to add a link to a page they cannot access.
+      $valid = \Drupal::service('access_manager')->checkNamedRoute($extracted['route_name'], $extracted['route_parameters'], \Drupal::currentUser());
+    }
+    if (!$valid) {
+      $this->setFormError('url', $form_state, $this->t("The path '@link_path' is either invalid or you do not have access to it.", array('@link_path' => $form_state['values']['url'])));
+    }
+    elseif ($extracted['route_name']) {
+      // The user entered a Drupal path.
+      $normal_path = $this->pathAliasManager->getPathByAlias($extracted['path']);
+      if ($extracted['path'] != $normal_path) {
+        drupal_set_message($this->t('The menu system stores system paths only, but will use the URL alias for display. %link_path has been stored as %normal_path', array(
+          '%link_path' => $extracted['path'],
+          '%normal_path' => $normal_path,
+        )));
+      }
+    }
+  }
+
+}
diff --git a/core/modules/menu_link_content/src/Tests/MenuLinkContentUITest.php b/core/modules/menu_link_content/src/Tests/MenuLinkContentUITest.php
new file mode 100644
index 0000000..052495a
--- /dev/null
+++ b/core/modules/menu_link_content/src/Tests/MenuLinkContentUITest.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Tests\MenuLinkContentUITest.
+ */
+
+namespace Drupal\menu_link_content\Tests;
+
+use Drupal\content_translation\Tests\ContentTranslationUITest;
+
+/**
+ * Tests the menu link content UI.
+ */
+class MenuLinkContentUITest extends ContentTranslationUITest {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array(
+    'language',
+    'content_translation',
+    'menu_link_content',
+    'menu_ui',
+  );
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Menu link content translation UI',
+      'description' => 'Tests the basic menu link content translation UI.',
+      'group' => 'Menu',
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    $this->entityTypeId = 'menu_link_content';
+    $this->bundle = 'menu_link_content';
+    $this->fieldName = 'title';
+    parent::setUp();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getTranslatorPermissions() {
+    return array_merge(parent::getTranslatorPermissions(), array('administer menu'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function createEntity($values, $langcode, $bundle_name = NULL) {
+    $values['menu_name'] = 'tools';
+    $values['route_name'] = 'menu_ui.overview_page';
+    $values['title'] = 'Test title';
+
+    return parent::createEntity($values, $langcode, $bundle_name);
+  }
+
+}
diff --git a/core/modules/menu_ui/src/Form/MenuLinkEditForm.php b/core/modules/menu_ui/src/Form/MenuLinkEditForm.php
new file mode 100644
index 0000000..5207e0f
--- /dev/null
+++ b/core/modules/menu_ui/src/Form/MenuLinkEditForm.php
@@ -0,0 +1,102 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_ui\Form\MenuLinkEditForm.
+ */
+
+namespace Drupal\menu_ui\Form;
+
+use Drupal\Core\DependencyInjection\ClassResolverInterface;
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Menu\MenuLinkInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Defines a generic edit form for all menu link plugin types.
+ */
+class MenuLinkEditForm extends FormBase {
+
+  /**
+   * The class resolver
+   *
+   * @var \Drupal\Core\DependencyInjection\ClassResolverInterface
+   */
+  protected $classResolver;
+
+  /**
+   * Constructs a MenuLinkEditForm object.
+   *
+   * @param \Drupal\Core\DependencyInjection\ClassResolverInterface $class_resolver
+   *   The class resolver.
+   */
+  public function __construct(ClassResolverInterface $class_resolver) {
+    $this->classResolver = $class_resolver;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('class_resolver')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'menu_link_edit';
+  }
+
+  /**
+   * {@inheritdoc}
+   *
+   * @param \Drupal\Core\Menu\MenuLinkInterface $menu_link_plugin
+   *   The plugin instance to use for this form.
+   */
+  public function buildForm(array $form, array &$form_state, MenuLinkInterface $menu_link_plugin = NULL) {
+
+    $form['menu_link_id'] = array(
+      '#type' => 'value',
+      '#value' => $menu_link_plugin->getPluginId(),
+    );
+    $class_name = $menu_link_plugin->getFormClass();
+    $form['#plugin_form'] = $this->classResolver->getInstanceFromDefinition($class_name);
+    $form['#plugin_form']->setMenuLinkInstance($menu_link_plugin);
+
+    $form += $form['#plugin_form']->buildConfigurationForm($form, $form_state);
+
+    $form['actions'] = array('#type' => 'actions');
+    $form['actions']['submit'] = array(
+      '#type' => 'submit',
+      '#value' => t('Save'),
+      '#button_type' => 'primary',
+    );
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function validateForm(array &$form, array &$form_state) {
+    $form['#plugin_form']->validateConfigurationForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, array &$form_state) {
+    $link = $form['#plugin_form']->submitConfigurationForm($form, $form_state);
+
+    drupal_set_message($this->t('The menu link has been saved.'));
+    $form_state['redirect_route'] = array(
+      'route_name' => 'menu_ui.menu_edit',
+      'route_parameters' => array(
+        'menu' => $link->getMenuName(),
+      ),
+    );
+  }
+
+}
diff --git a/core/modules/system/config/install/menu_link.static.overrides.yml b/core/modules/system/config/install/menu_link.static.overrides.yml
new file mode 100644
index 0000000..ca4ba7f
--- /dev/null
+++ b/core/modules/system/config/install/menu_link.static.overrides.yml
@@ -0,0 +1 @@
+definitions: []
diff --git a/core/modules/system/config/schema/system.schema.yml b/core/modules/system/config/schema/system.schema.yml
index 5375524..77e1382 100644
--- a/core/modules/system/config/schema/system.schema.yml
+++ b/core/modules/system/config/schema/system.schema.yml
@@ -391,3 +391,30 @@ condition.plugin.request_path:
   mapping:
     pages:
       type: string
+
+menu_link.static.overrides:
+  type: mapping
+  label: 'Menu link overrides'
+  mapping:
+    definitions:
+      type: sequence
+      label: Definitions
+      sequence:
+        - type: mapping
+          label: Definition
+          mapping:
+            menu_name:
+              type: string
+              label: 'Menu name'
+            parent:
+              type: string
+              label: 'Parent'
+            weight:
+              type: integer
+              label: 'Weight'
+            expanded:
+              type: boolean
+              label: 'Expanded'
+            hidden:
+              type: boolean
+              label: 'Hidden'
diff --git a/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php b/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php
index c2308dd..2bb590a 100644
--- a/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/DefaultMenuLinkTreeManipulatorsTest.php
@@ -95,17 +95,17 @@ protected function mockTree() {
       8 => MenuLinkMock::create(array('id' => 'test.example8', 'route_name' => 'example8', 'title' => 'quxqux', 'parent' => '')),
     );
     $this->originalTree = array();
-    $this->originalTree[1] = new MenuLinkTreeElement($this->links[1], FALSE, 1, FALSE, array());
-    $this->originalTree[2] = new MenuLinkTreeElement($this->links[2], TRUE, 1, FALSE, array(
-      3 => new MenuLinkTreeElement($this->links[3], TRUE, 2, FALSE, array(
-        4 => new MenuLinkTreeElement($this->links[4], FALSE, 3, FALSE, array()),
+    $this->originalTree[1] = new TestMenuLinkTreeElement($this->links[1], FALSE, 1, FALSE, array());
+    $this->originalTree[2] = new TestMenuLinkTreeElement($this->links[2], TRUE, 1, FALSE, array(
+      3 => new TestMenuLinkTreeElement($this->links[3], TRUE, 2, FALSE, array(
+        4 => new TestMenuLinkTreeElement($this->links[4], FALSE, 3, FALSE, array()),
       )),
     ));
-    $this->originalTree[5] = new MenuLinkTreeElement($this->links[5], TRUE, 1, FALSE, array(
-      7 => new MenuLinkTreeElement($this->links[7], FALSE, 2, FALSE, array()),
+    $this->originalTree[5] = new TestMenuLinkTreeElement($this->links[5], TRUE, 1, FALSE, array(
+      7 => new TestMenuLinkTreeElement($this->links[7], FALSE, 2, FALSE, array()),
     ));
-    $this->originalTree[6] = new MenuLinkTreeElement($this->links[6], FALSE, 1, FALSE, array());
-    $this->originalTree[8] = new MenuLinkTreeElement($this->links[8], FALSE, 1, FALSE, array());
+    $this->originalTree[6] = new TestMenuLinkTreeElement($this->links[6], FALSE, 1, FALSE, array());
+    $this->originalTree[8] = new TestMenuLinkTreeElement($this->links[8], FALSE, 1, FALSE, array());
   }
 
   /**
@@ -119,16 +119,19 @@ public function testGenerateIndexAndSort() {
     $tree = $this->defaultMenuTreeManipulators->generateIndexAndSort($tree);
 
     // Validate that parent elements #1, #2, #5 and #6 exist on the root level.
-    $this->assertEquals($this->links[1]->getPluginId(), $tree['50000 foo test.example1']->link->getPluginId());
-    $this->assertEquals($this->links[2]->getPluginId(), $tree['50000 bar test.example2']->link->getPluginId());
-    $this->assertEquals($this->links[5]->getPluginId(), $tree['50000 foofoo test.example5']->link->getPluginId());
-    $this->assertEquals($this->links[6]->getPluginId(), $tree['50000 barbar test.example6']->link->getPluginId());
-    $this->assertEquals($this->links[8]->getPluginId(), $tree['50000 quxqux test.example8']->link->getPluginId());
+    $this->assertEquals($this->links[1]->getPluginId(), $tree['50000 foo test.example1']->getLink()->getPluginId());
+    $this->assertEquals($this->links[2]->getPluginId(), $tree['50000 bar test.example2']->getLink()->getPluginId());
+    $this->assertEquals($this->links[5]->getPluginId(), $tree['50000 foofoo test.example5']->getLink()->getPluginId());
+    $this->assertEquals($this->links[6]->getPluginId(), $tree['50000 barbar test.example6']->getLink()->getPluginId());
+    $this->assertEquals($this->links[8]->getPluginId(), $tree['50000 quxqux test.example8']->getLink()->getPluginId());
 
     // Verify that child element #4 is at the correct location in the hierarchy.
-    $this->assertEquals($this->links[4]->getPluginId(), $tree['50000 bar test.example2']->subtree['50000 baz test.example3']->subtree['50000 qux test.example4']->link->getPluginId());
+    $subtree = $tree['50000 bar test.example2']->getSubtree();
+    $subtree = $subtree['50000 baz test.example3']->getSubtree();
+    $this->assertEquals($this->links[4]->getPluginId(), $subtree['50000 qux test.example4']->getLink()->getPluginId());
     // Verify that child element #7 is at the correct location in the hierarchy.
-    $this->assertEquals($this->links[7]->getPluginId(), $tree['50000 foofoo test.example5']->subtree['50000 bazbaz test.example7']->link->getPluginId());
+    $subtree = $tree['50000 foofoo test.example5']->getSubtree();
+    $this->assertEquals($this->links[7]->getPluginId(), $subtree['50000 bazbaz test.example7']->getLink()->getPluginId());
   }
 
   /**
@@ -151,8 +154,9 @@ public function testCheckAccess() {
       )));
 
     $this->mockTree();
-    $this->originalTree[5]->subtree[7]->access = TRUE;
-    $this->originalTree[8]->access = FALSE;
+    $subtree = $this->originalTree[5]->getSubtree();
+    $subtree[7]->setAccessible(TRUE);
+    $this->originalTree[8]->setAccessible(FALSE);
 
     $tree = $this->defaultMenuTreeManipulators->checkAccess($this->originalTree);
 
@@ -160,21 +164,22 @@ public function testCheckAccess() {
     $this->assertFalse(array_key_exists(1, $tree));
     // Menu link 2: route with parameters, access granted.
     $element = $tree[2];
-    $this->assertTrue($element->access);
+    $this->assertTrue($element->isAccessible());
     // Menu link 3: route with parameters, access forbidden, hence removed,
     // including its children.
-    $this->assertFalse(array_key_exists(3, $tree[2]->subtree));
+    $this->assertFalse(array_key_exists(3, $tree[2]->getSubtree()));
     // Menu link 4: child of menu link 3, which already is removed.
-    $this->assertSame(array(), $tree[2]->subtree);
+    $this->assertSame(array(), $tree[2]->getSubtree());
     // Menu link 5: no route name, treated as external, hence access granted.
     $element = $tree[5];
-    $this->assertTrue($element->access);
+    $this->assertTrue($element->isAccessible());
     // Menu link 6: external URL, hence access granted.
     $element = $tree[6];
-    $this->assertTrue($element->access);
+    $this->assertTrue($element->isAccessible());
     // Menu link 7: 'access' already set.
-    $element = $tree[5]->subtree[7];
-    $this->assertTrue($element->access);
+    $subtree = $tree[5]->getSubtree();
+    $element = $subtree[7];
+    $this->assertTrue($element->isAccessible());
     // Menu link 8: 'access' already set, to FALSE, hence removed.
     $this->assertFalse(array_key_exists(8, $tree));
   }
@@ -211,7 +216,7 @@ public function testExtractSubtreeOfActiveTrail() {
 
     // Link 5 in the active trail.
     $this->mockTree();
-    $this->originalTree[5]->inActiveTrail = TRUE;
+    $this->originalTree[5]->setInActiveTrail(TRUE);
     // Get level 0.
     $tree = $this->defaultMenuTreeManipulators->extractSubtreeOfActiveTrail($this->originalTree, 0);
     $this->assertEquals(array(1, 2, 5, 6, 8), array_keys($tree));
@@ -224,7 +229,7 @@ public function testExtractSubtreeOfActiveTrail() {
 
     // Link 2 in the active trail.
     $this->mockTree();
-    $this->originalTree[2]->inActiveTrail = TRUE;
+    $this->originalTree[2]->setInActiveTrail(TRUE);
     // Get level 0.
     $tree = $this->defaultMenuTreeManipulators->extractSubtreeOfActiveTrail($this->originalTree, 0);
     $this->assertEquals(array(1, 2, 5, 6, 8), array_keys($tree));
@@ -237,8 +242,9 @@ public function testExtractSubtreeOfActiveTrail() {
 
     // Links 2 and 3 in the active trail.
     $this->mockTree();
-    $this->originalTree[2]->inActiveTrail = TRUE;
-    $this->originalTree[2]->subtree[3]->inActiveTrail = TRUE;
+    $this->originalTree[2]->setInActiveTrail(TRUE);
+    $subtree = $this->originalTree[2]->getSubtree();
+    $subtree[3]->setInActiveTrail(TRUE);
     // Get level 0.
     $tree = $this->defaultMenuTreeManipulators->extractSubtreeOfActiveTrail($this->originalTree, 0);
     $this->assertEquals(array(1, 2, 5, 6, 8), array_keys($tree));
@@ -251,3 +257,11 @@ public function testExtractSubtreeOfActiveTrail() {
   }
 
 }
+
+class TestMenuLinkTreeElement extends MenuLinkTreeElement {
+
+  public function setInActiveTrail($in_active_trail) {
+    $this->inActiveTrail = $in_active_trail;
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Menu/MenuLinkTreeElementTest.php b/core/tests/Drupal/Tests/Core/Menu/MenuLinkTreeElementTest.php
index da1a318..4a4accb 100644
--- a/core/tests/Drupal/Tests/Core/Menu/MenuLinkTreeElementTest.php
+++ b/core/tests/Drupal/Tests/Core/Menu/MenuLinkTreeElementTest.php
@@ -20,18 +20,30 @@
 class MenuLinkTreeElementTest extends UnitTestCase {
 
   /**
-   * Tests construction.
+   * Tests construction and setters.
    *
    * @covers ::__construct
+   * @covers ::setAccessible
+   * @covers ::setSubtree
    */
   public function testConstruction() {
     $link = MenuLinkMock::create(array('id' => 'test'));
     $item = new MenuLinkTreeElement($link, FALSE, 3, FALSE, array());
-    $this->assertSame($link, $item->link);
-    $this->assertSame(FALSE, $item->hasChildren);
-    $this->assertSame(3, $item->depth);
-    $this->assertSame(FALSE, $item->inActiveTrail);
-    $this->assertSame(array(), $item->subtree);
+    $this->assertSame($link, $item->getLink());
+    $this->assertSame(FALSE, $item->hasChildren());
+    $this->assertSame(3, $item->getDepth());
+    $this->assertSame(FALSE, $item->isInActiveTrail());
+    $this->assertSame(array(), $item->getSubtree());
+    $this->assertSame(NULL, $item->isAccessible());
+
+    $item->setAccessible(TRUE);
+    $this->assertSame(TRUE, $item->isAccessible());
+    $item->setAccessible(FALSE);
+    $this->assertSame(FALSE, $item->isAccessible());
+
+    $subtree = array(new MenuLinkTreeElement(MenuLinkMock::create(array('id' => 'foobar')), FALSE, 4, FALSE, array()));
+    $item->setSubtree($subtree);
+    $this->assertSame($subtree, $item->getSubtree());
   }
 
   /**
