diff --git a/core/config/install/menu_link.static.overrides.yml b/core/config/install/menu_link.static.overrides.yml
new file mode 100644
index 0000000..ca4ba7f
--- /dev/null
+++ b/core/config/install/menu_link.static.overrides.yml
@@ -0,0 +1 @@
+definitions: []
diff --git a/core/config/schema/core.menu.schema.yml b/core/config/schema/core.menu.schema.yml
new file mode 100644
index 0000000..72ab4a1
--- /dev/null
+++ b/core/config/schema/core.menu.schema.yml
@@ -0,0 +1,28 @@
+
+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/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php b/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php
new file mode 100644
index 0000000..ead546d
--- /dev/null
+++ b/core/lib/Drupal/Core/Menu/Form/MenuLinkDefaultForm.php
@@ -0,0 +1,213 @@
+<?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 parent form selector 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 \Drupal\Core\Menu\Form\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.
+   *
+   * @param string $module
+   *   A module machine name.
+   *
+   * @todo This function is horrible, but core has nothing better until we add a
+   * a method to the ModuleHandler that handles this nicely.
+   * https://drupal.org/node/2281989
+   *
+   * @return string
+   *   The human-readable, localized module name, or the machine name passed in
+   *   if no matching module is found.
+   */
+  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..1e2e6f1
--- /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 plugin instance.
+   *
+   * @param \Drupal\Core\Menu\MenuLinkInterface $menu_link
+   *   A menu link plugin instance.
+   */
+  public function setMenuLinkInstance(MenuLinkInterface $menu_link);
+
+  /**
+   * Form plugin helper which extracts a plugin definition from form values.
+   *
+   * @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 taken from the form values.
+   */
+  public function extractFormValues(array &$form, array &$form_state);
+
+}
diff --git a/core/modules/menu_link_content/menu_link_content.links.task.yml b/core/modules/menu_link_content/menu_link_content.links.task.yml
new file mode 100644
index 0000000..8195236
--- /dev/null
+++ b/core/modules/menu_link_content/menu_link_content.links.task.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..81bbd40
--- /dev/null
+++ b/core/modules/menu_link_content/src/Controller/MenuController.php
@@ -0,0 +1,37 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Controller\MenuController.
+ */
+
+namespace Drupal\menu_link_content\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\system\MenuInterface;
+
+/**
+ * Defines a route controller for a form for menu link content entity creation.
+ */
+class MenuController extends ControllerBase {
+
+  /**
+   * Provides the menu link creation form.
+   *
+   * @param \Drupal\system\MenuInterface $menu
+   *   An entity representing a custom menu.
+   *
+   * @return array
+   *   Returns the menu link creation 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/Entity/MenuLinkContent.php b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
index 411adf1..36600a6 100644
--- a/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
+++ b/core/modules/menu_link_content/src/Entity/MenuLinkContent.php
@@ -336,27 +336,6 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
       ->setDescription(t('A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).'))
       ->setSetting('default_value', FALSE);
 
-    // The form widget doesn't work yet for core fields, so we skip the
-    // for display and manually create form elements for the boolean fields.
-    // @see https://drupal.org/node/2226493
-    // @see https://drupal.org/node/2150511
-    $fields['expanded'] = FieldDefinition::create('boolean')
-      ->setLabel(t('Expanded'))
-      ->setDescription(t('Flag for whether this link should be rendered as expanded in menus - expanded links always have their child links displayed, instead of only when the link is in the active trail (1 = expanded, 0 = not expanded).'))
-      ->setSetting('default_value', FALSE)
-      ->setDisplayOptions('view', array(
-        'label' => 'hidden',
-        'type' => 'boolean',
-        'weight' => 0,
-      ));
-
-    // We manually create a form element for this, since the form logic is
-    // is inverted to show enabled.
-    $fields['hidden'] = FieldDefinition::create('boolean')
-      ->setLabel(t('Hidden'))
-      ->setDescription(t('A flag for whether the link should be hidden in menus or rendered normally.'))
-      ->setSetting('default_value', FALSE);
-
     $fields['weight'] = FieldDefinition::create('integer')
       ->setLabel(t('Weight'))
       ->setDescription(t('Link weight among links in the same menu at the same depth.'))
@@ -371,6 +350,28 @@ public static function baseFieldDefinitions(EntityTypeInterface $entity_type) {
         'weight' => 0,
       ));
 
+    $fields['expanded'] = FieldDefinition::create('boolean')
+      ->setLabel(t('Show as expanded'))
+      ->setDescription(t('If selected and this menu link has children, the menu will always appear expanded.'))
+      ->setSetting('default_value', FALSE)
+      ->setDisplayOptions('view', array(
+        'label' => 'hidden',
+        'type' => 'boolean',
+        'weight' => 0,
+      ))
+    ->setDisplayOptions('form', array(
+        'settings' => array('display_label' => TRUE),
+        'weight' => 0,
+      ));
+
+    // @todo we manually create a form element for this, since the form logic is
+    // is inverted to show enabled.  Flip this to a status field and use the
+    // normal entity boolen widget https://www.drupal.org/node/2305707
+    $fields['hidden'] = FieldDefinition::create('boolean')
+      ->setLabel(t('Hidden'))
+      ->setDescription(t('A flag for whether the link should be hidden in menus or rendered normally.'))
+      ->setSetting('default_value', FALSE);
+
     $fields['langcode'] = FieldDefinition::create('language')
       ->setLabel(t('Language code'))
       ->setDescription(t('The node language code.'));
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..6c75d04
--- /dev/null
+++ b/core/modules/menu_link_content/src/Form/MenuLinkContentDeleteForm.php
@@ -0,0 +1,78 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Form\MenuLinkContentDeleteForm.
+ */
+
+namespace Drupal\menu_link_content\Form;
+
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\Core\Entity\ContentEntityConfirmFormBase;
+use Drupal\Core\Entity\EntityManagerInterface;
+use Drupal\Core\Url;
+use Drupal\Core\Logger\LoggerChannelFactoryInterface;
+
+/**
+ * Provides a delete form for content menu links.
+ */
+class MenuLinkContentDeleteForm extends ContentEntityConfirmFormBase {
+
+  /**
+   * Logger channel.
+   *
+   * @var \Drupal\Core\Logger\LoggerChannelInterface
+   */
+  protected $logger;
+
+  /**
+   * Constructs a ContentEntityForm object.
+   *
+   * @param \Drupal\Core\Entity\EntityManagerInterface $entity_manager
+   *   The entity manager.
+   * @param \Drupal\Core\Logger\LoggerChannelFactoryInterface $logger_factory
+   *   The logger channel factory.
+   */
+  public function __construct(EntityManagerInterface $entity_manager, LoggerChannelFactoryInterface $logger_factory) {
+    parent::__construct($entity_manager);
+    $this->logger = $logger_factory->get('menu');
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+      $container->get('entity.manager'),
+      $container->get('logger.factory')
+    );
+  }
+
+  /**
+   * {@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) {
+    $t_args = array('%title' => $this->entity->getTitle());
+    $this->entity->delete();
+    drupal_set_message($this->t('The menu link %title has been deleted.', $t_args));
+    $this->logger->notice('Deleted menu link %title.', $t_args);
+    $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..fd4479f
--- /dev/null
+++ b/core/modules/menu_link_content/src/Form/MenuLinkContentForm.php
@@ -0,0 +1,428 @@
+<?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\Access\AccessManager;
+use Drupal\Core\Session\AccountInterface;
+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\MenuLinkContentInterface
+   */
+  protected $entity;
+
+  /**
+   * The parent form selector 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;
+
+  /**
+   * The access manager.
+   *
+   * @var \Drupal\Core\Access\AccessManager
+   */
+  protected $accessManager;
+
+  /**
+   * The current user.
+   *
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $account;
+
+  /**
+   * 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.
+   * @param \Drupal\Core\Access\AccessManager $access_manager
+   *   The access manager.
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   The current user.
+   */
+  public function __construct(EntityManagerInterface $entity_manager, MenuParentFormSelectorInterface $menu_parent_selector, AliasManagerInterface $alias_manager, ModuleHandlerInterface $module_handler, RequestContext $request_context, LanguageManagerInterface $language_manager, AccessManager $access_manager, AccountInterface $account) {
+    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;
+    $this->accessManager = $access_manager;
+    $this->account = $account;
+  }
+
+  /**
+   * {@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'),
+      $container->get('access_manager'),
+      $container->get('current_user')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setMenuLinkInstance(MenuLinkInterface $menu_link) {
+    // Load the entity for the entity form. Loading by entity ID is much faster
+    // than loading by uuid, so use that ID if we have it.
+    $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 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;
+  }
+
+  /**
+   * Breaks 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);
+
+    // 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 add a helper method to Url to render just the query string and
+      // fragment. https://www.drupal.org/node/2305013
+      $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,
+      '#weight' => -2,
+    );
+
+    $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(),
+      '#weight' => 0,
+    );
+
+    $default = $this->entity->getMenuName() . ':' . $this->entity->getParentId();
+    $form['menu_parent'] = $this->menuParentSelector->parentSelectElement($default, $this->entity->getPluginId());
+    $form['menu_parent']['#weight'] = 10;
+    $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($this->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($this->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 = $this->accessManager->checkNamedRoute($extracted['route_name'], $extracted['route_parameters'], $this->account);
+    }
+    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..c805578
--- /dev/null
+++ b/core/modules/menu_link_content/src/Tests/MenuLinkContentUITest.php
@@ -0,0 +1,59 @@
+<?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.
+ *
+ * @group Menu
+ */
+class MenuLinkContentUITest extends ContentTranslationUITest {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array(
+    'language',
+    'content_translation',
+    'menu_link_content',
+    'menu_ui',
+  );
+
+  /**
+   * {@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/menu_ui.admin.inc b/core/modules/menu_ui/menu_ui.admin.inc
index 0d7a0fd..579a703 100644
--- a/core/modules/menu_ui/menu_ui.admin.inc
+++ b/core/modules/menu_ui/menu_ui.admin.inc
@@ -6,6 +6,7 @@
  */
 
 use Drupal\Core\Render\Element;
+use Drupal\Component\Utility\SafeMarkup;
 
 /**
  * Returns HTML for the menu overview form into a table.
@@ -46,8 +47,8 @@ function theme_menu_overview_form($variables) {
 
       $row = array();
       $row[] = drupal_render($indent) . drupal_render($element['title']);
-      $row[] = array('data' => drupal_render($element['hidden']), 'class' => array('checkbox', 'menu-enabled'));
-      $row[] = drupal_render($element['weight']) . drupal_render($element['plid']) . drupal_render($element['mlid']);
+      $row[] = array('data' => drupal_render($element['enabled']), 'class' => array('checkbox', 'menu-enabled'));
+      $row[] = SafeMarkup::set(drupal_render($element['weight']) . drupal_render($element['parent']) . drupal_render($element['id']));
       $row[] = drupal_render($element['operations']);
 
       $row = array_merge(array('data' => $row), $element['#attributes']);
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..26ecb95
--- /dev/null
+++ b/core/modules/menu_ui/src/Form/MenuLinkEditForm.php
@@ -0,0 +1,101 @@
+<?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' => $this->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(),
+      ),
+    );
+  }
+
+}
