diff --git a/README.md b/README.md
index ae737b59e5a195fe6b17fbfceb7f518b08799778..61455023072872a19bbbab15b6a842dc2bb68062 100644
--- a/README.md
+++ b/README.md
@@ -21,7 +21,7 @@ REQUIREMENTS
 
 This module requires the following library:
 
-* Fancytree JS (This module will automatically load this library from romte CDN if it wasn't hosted locally under /libraries/jquery.fancytree/ folder)
+* jsTree JS (This module will automatically load this library from romte CDN if it wasn't hosted locally under /libraries/jquery.jstree/3.3.8/ folder)
 
 INSTALLATION
 ------------
diff --git a/hierarchy_manager.module b/hierarchy_manager.module
index 0f5bcb8c94ffd6d95c4c1d1c54a93bf7c549c2a9..574aece1856a370234200771d11d33998090e944 100644
--- a/hierarchy_manager.module
+++ b/hierarchy_manager.module
@@ -29,6 +29,18 @@ function hierarchy_manager_library_info_alter(array &$libraries, $module) {
   }
 }
 
+/**
+ * Implement hook_entity_type_alter().
+ * 
+ * @param array $entity_types
+ *   Entity type information array.
+ */
+function hierarchy_manager_entity_type_alter(array &$entity_types) {
+  // Override the menu edit form.
+  $entity_types['menu']
+  ->setFormClass('edit', 'Drupal\hierarchy_manager\Form\HmMenuForm');
+}
+
 /**
  * Replace local library with CDN.
  *
diff --git a/hierarchy_manager.routing.yml b/hierarchy_manager.routing.yml
index ca0b1727d02e71f2dbc7c30a86b93fc94ffb70c7..38b2518ae4885738cd12016bf2698fcff7944eb1 100644
--- a/hierarchy_manager.routing.yml
+++ b/hierarchy_manager.routing.yml
@@ -9,7 +9,7 @@ hierarchy_manager.hm_config_form:
   options:
     _admin_route: TRUE
 
-# Taxonomy display plugin.
+# Taxonomy hierarchy plugin.
 hierarchy_manager.taxonomy.tree.json:
   path: '/admin/hierarchy_manager/taxonomy/json/{vid}'
   defaults:
@@ -29,3 +29,22 @@ hierarchy_manager.taxonomy.tree.update:
   options:
     _admin_route: TRUE   
  
+ # Menu hierarchy plugin.
+hierarchy_manager.menu.tree.json:
+  path: '/admin/hierarchy_manager/menu/json/{mid}'
+  defaults:
+    _title: 'Menu tree'
+    _controller: '\Drupal\hierarchy_manager\Controller\HmMenuController::menuTreeJson'
+  requirements:
+    _permission: 'administer menu'
+  options:
+    _admin_route: TRUE
+hierarchy_manager.menu.tree.update:
+  path: '/admin/hierarchy_manager/menu/update/{mid}'
+  defaults:
+    _title: 'Menu tree'
+    _controller: '\Drupal\hierarchy_manager\Controller\HmMenuController::updateMenuLinks'
+  requirements:
+    _permission: 'administer menu'
+  options:
+    _admin_route: TRUE   
diff --git a/hierarchy_manager.services.yml b/hierarchy_manager.services.yml
index f65c06e80f514220752ecebc3be491edf45260a9..a3b266688ff016d5c30fa6d1e8b7b0f171211f43 100644
--- a/hierarchy_manager.services.yml
+++ b/hierarchy_manager.services.yml
@@ -1,11 +1,19 @@
 services:
+  # Plugins
   plugin.manager.hm.hmsetup:
     class: Drupal\hierarchy_manager\Plugin\HmSetupPluginManager
     parent: default_plugin_manager
   plugin.manager.hm.display_plugin:
     class: Drupal\hierarchy_manager\Plugin\HmDisplayPluginManager
     parent: default_plugin_manager
+  # Event subscriber
   hm.route_subscriber:
     class: Drupal\hierarchy_manager\Routing\HmRouteSubscriber
     tags:
       - { name: event_subscriber }
+  # Custom services
+  hm.plugin_type_manager:
+    class: Drupal\hierarchy_manager\PluginTypeManager
+    arguments: ['@entity_type.manager', '@plugin.manager.hm.display_plugin', '@plugin.manager.hm.hmsetup']
+    tags:
+      - { name: hm_plugin_type_manager, priority: 1000 }
diff --git a/js/Plugin/jstree/hm.jstree.js b/js/Plugin/jstree/hm.jstree.js
index 69ad563898868e09648104f0f43794a865309d47..62eea15e4da5c30cdc127f837bd7c04fabe55262 100644
--- a/js/Plugin/jstree/hm.jstree.js
+++ b/js/Plugin/jstree/hm.jstree.js
@@ -20,6 +20,7 @@
           const updateURL = treeContainer.attr('url-update')
           let reload = true;
           let rollback = false;
+          let after = 1;
           // Ajax callback to refresh the tree.
           if (reload) {
             // Build the tree.
@@ -54,12 +55,26 @@
               const thisTree = data.instance;
               const movedNode = data.node;
               
-              if (!rollback) {            
+              if (!rollback) {
+                let list = thisTree.get_node(data.parent).children;
+                let before = '';
+                let after = '';
+                if (data.position > 0) {
+                  before = list[data.position - 1];
+                }
+                
+                if (data.position < list.length - 1) {
+                  after = list[data.position + 1];
+                }
+
+                let parent = data.parent === '#' ? 0 : data.parent;
                 // Update the data on server side.
                 $.post(updateURL, {
                   keys: [movedNode.id],
                   target: data.position,
-                  parent: data.parent
+                  parent: parent,
+                  after: after,
+                  before: before
                 })
                   .done(function(response) {
                     if (response.result !== "success") {
diff --git a/src/Controller/HmMenuController.php b/src/Controller/HmMenuController.php
new file mode 100644
index 0000000000000000000000000000000000000000..9f2793a10c27ac5d47bd0821be594fe44998b812
--- /dev/null
+++ b/src/Controller/HmMenuController.php
@@ -0,0 +1,372 @@
+<?php
+
+namespace Drupal\hierarchy_manager\Controller;
+
+use Drupal\Core\Url;
+use Drupal\Core\Access\CsrfTokenGenerator;
+use Drupal\Core\Controller\ControllerBase;
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\Core\Menu\MenuLinkTreeInterface;
+use Drupal\Core\Menu\MenuLinkManagerInterface;
+use Drupal\Core\Menu\MenuTreeParameters;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Symfony\Component\HttpFoundation\Request;
+use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpFoundation\JsonResponse;
+
+class HmMenuController extends ControllerBase {
+  
+  /**
+   * CSRF Token.
+   *
+   * @var \Drupal\Core\Access\CsrfTokenGenerator
+   */
+  protected $csrfToken;
+  
+  /**
+   * The menu_link_content storage handler.
+   *
+   * @var \Drupal\menu_link_content\MenuLinkContentStorageInterface
+   */
+  protected $storageController;
+  
+  /**
+   * The hierarchy manager plugin type manager.
+   *
+   * @var \Drupal\hierarchy_manager\PluginTypeManager
+   */
+  protected $hmPluginTypeManager;
+  
+  /**
+   * The menu tree service.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkTreeInterface
+   */
+  protected $menuTree;
+  
+  /**
+   * The menu tree array.
+   *
+   * @var array
+   */
+  protected $overviewTree = [];
+  
+  /**
+   * The menu link manager.
+   *
+   * @var \Drupal\Core\Menu\MenuLinkManagerInterface
+   */
+  protected $menuLinkManager;
+  
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(CsrfTokenGenerator $csrfToken, EntityTypeManagerInterface $entity_type_manager, $plugin_type_manager, MenuLinkTreeInterface $menu_tree, MenuLinkManagerInterface $menu_link_manager) {    
+    $this->csrfToken = $csrfToken;
+    $this->entityTypeManager = $entity_type_manager;
+    $this->storageController = $entity_type_manager->getStorage('menu_link_content');
+    $this->hmPluginTypeManager = $plugin_type_manager;
+    $this->menuTree = $menu_tree;
+    $this->menuLinkManager = $menu_link_manager;
+  }
+  
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container) {
+    return new static(
+        $container->get('csrf_token'),
+        $container->get('entity_type.manager'),
+        $container->get('hm.plugin_type_manager'),
+        $container->get('menu.link_tree'),
+        $container->get('plugin.manager.menu.link')
+        );
+  }
+  
+  /**
+   * Callback for menu tree json.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   Http request object.
+   * @param string $mid
+   *   Menu ID.
+   */
+  public function menuTreeJson(Request $request, string $mid) {
+    // Access token.
+    $token = $request->get('token');
+    
+    if (empty($token) || !$this->csrfToken->validate($token, $mid)) {
+      return new Response($this->t('Access denied!'));
+    }
+    
+    $parent = $request->get('parent');
+    $depth = $request->get('depth');
+    $destination = $request->get('destination');
+    
+    if (empty($depth)) {
+      $depth = 0;
+    }
+    else {
+      $depth = intval($depth);
+    }
+    
+    if (empty($parent)) {
+      $parent = '';
+    }
+    
+    if (empty($destination)) {
+      $destination = '';
+    }
+    // We indicate that a menu administrator is running the menu access check.
+    $request->attributes->set('_menu_admin', TRUE);
+    
+    $tree = $this->loadMenuTree($mid, $parent, $depth, $destination);
+    
+    // menu access check done.
+    $request->attributes->set('_menu_admin', FALSE);
+    
+    if ($tree) {
+      // Display plugin instance.
+      $display_plugin = $this->getDisplayPlugin();
+      
+      if (empty($display_plugin)) {
+        return new JsonResponse(['result' => 'Display profile has not been set up.']);
+      }
+      
+      if (method_exists($display_plugin, 'treeData')) {
+        // Transform the tree data to the structure
+        // that display plugin accepts.
+        $tree_data = $display_plugin->treeData($tree);
+      }
+      else {
+        $tree_data = $tree;
+      }
+      
+      return new JsonResponse($tree_data);
+    }
+    
+    return new JsonResponse([]);
+  }
+  
+  /**
+   * Callback for taxonomy tree json.
+   *
+   * @param \Symfony\Component\HttpFoundation\Request $request
+   *   Http request object.
+   * @param string $vid
+   *   Vocabulary ID.
+   */
+  public function updateMenuLinks(Request $request, string $mid) {
+    // Access token.
+    $token = $request->get('token');
+    if (empty($token) || !$this->csrfToken->validate($token, $mid)) {
+      return new Response($this->t('Access denied!'));
+    }
+    
+    $target_position = $request->get('target');
+    $parent = $request->get('parent');
+    $updated_links = $request->get('keys');
+    //$after = $request->get('after');
+    $before = $request->get('before');
+    
+    if (is_array($updated_links) && !empty($updated_links)) {
+      if (empty($parent)) {
+        // Root is the parent.
+        $parent = '';
+        $parent_links = $children = $this->loadMenuLinkObjs($mid, $parent, 1);
+      }
+      else {
+        // All children menu links (depth = 1).
+        $parent_links = $this->loadMenuLinkObjs($mid, $parent, 1);
+      }  
+      // In order to make room for menu links inserted,
+      // we need to move all children links forward,
+      // and work out the weight for links inserted.
+      if (empty($parent_links)) {
+          // The parent menu doesn't exist.
+          return new JsonResponse(['result' => 'fail']);
+      }
+      else {
+        if (empty($children)) {
+          $parent_link = reset($parent_links);
+          $children = $parent_link->subtree;
+        }
+
+        if ($children) {
+          // The parent menu has children.
+          $target_position = intval($target_position);
+          $all_siblings = [];
+          $insert_after = TRUE;
+          $position = 0;
+          foreach ($children as $child) {
+            $link = $child->link;
+            $link_id = $link->getPLuginId();
+            // Figure out if the new links are inserted
+            // after the target position.
+            if ($position++ == $target_position && $link_id !== $before) {
+              $insert_after = FALSE;
+            }
+            
+            $all_siblings[$link_id] = (int) $link->getWeight();
+          }
+          
+          $new_hierarchy = $this->hmPluginTypeManager->updateHierarchy($target_position, $all_siblings, $updated_links, $insert_after);
+          $weight = $new_hierarchy['start_weight'];
+          $moving_siblings = $new_hierarchy['moving_siblings'];
+          
+          // Update all sibling links needed to update.
+          foreach ($moving_siblings as $link_id => $link_weight) {
+            $this->menuLinkManager->updateDefinition($link_id, ['weight' => $link_weight]);
+          }
+        }
+        else {
+          // The parent link doesn't have children.
+          $weight = 0;
+        }
+        // Move all links updated.
+        foreach ($updated_links as $link_id) {
+          $this->menuLinkManager->updateDefinition($link_id, ['weight' => $weight++, 'parent' => $parent]);
+        }
+      }
+      
+      return new JsonResponse(['result' => 'success']);
+    }
+    
+    return new JsonResponse(['result' => 'fail']);
+  }
+  
+  /**
+   * Get a display plugin instance.
+   * 
+   * @return NULL|object
+   */
+  protected function getDisplayPlugin() {
+    return $this->hmPluginTypeManager->getDisplayPluginInstance('hm_setup_menu');
+  }
+  
+  /**
+   * Load menu links into one array.
+   * 
+   * @param string $mid
+   *   The menu ID.
+   * @param string $parent
+   *   parent id
+   * @param int $depth
+   *   The max depth loaded.
+   * @param string $destination
+   *   The destination of edit link.
+   */
+  protected  function loadMenuTree(string $mid, string $parent, int $depth = 0, string $destination = '') {
+    $tree = $this->loadMenuLinkObjs($mid, $parent, $depth);
+    // Load all menu links into one array.
+    $tree = $this->buildMenuLinkArray($tree);
+    $links = [];
+    foreach ($tree as $element) {
+      if (!empty($destination)) {
+        $element['url'] = $element['url'] . '?destination=' . $destination;
+      }
+      $links[] = $this->hmPluginTypeManager->buildHierarchyItem(
+          $element['id'],
+          $element['title'],
+          $element['parent'],
+          $element['url']);
+    }
+    
+    return $links;
+  }
+  
+  /**
+   * Load menu links into one array.
+   *
+   * @param string $mid
+   *   The menu ID.
+   * @param string $parent
+   *   parent id
+   * @param int $depth
+   *   The max depth loaded.
+   * @param string $destination
+   *   The destination of edit link.
+   */
+  protected  function loadMenuLinkObjs(string $mid, string $parent, int $depth = 0) {
+    $menu_para = new MenuTreeParameters();
+    if (!empty($depth)) {
+      $menu_para->setMaxDepth($depth);
+    }
+    if (!empty($parent)) {
+      $menu_para->setRoot($parent);
+    }
+    $tree = $this->menuTree->load($mid, $menu_para);
+    $manipulators = [
+      ['callable' => 'menu.default_tree_manipulators:checkAccess'],
+      ['callable' => 'menu.default_tree_manipulators:generateIndexAndSort'],
+    ];
+ 
+    return $tree = $this->menuTree->transform($tree, $manipulators);
+  }
+  
+  /**
+   * Recursive helper function for loadMenuTree().
+   *
+   * @param \Drupal\Core\Menu\MenuLinkTreeElement[] $tree
+   *   The tree retrieved by \Drupal\Core\Menu\MenuLinkTreeInterface::load().
+   *
+   * @return array
+   *   The menu links array.
+   */
+  protected function buildMenuLinkArray($tree) {
+//    $tree_access_cacheability = new CacheableMetadata();
+    foreach ($tree as $element) {
+//      $tree_access_cacheability = $tree_access_cacheability->merge(CacheableMetadata::createFromObject($element->access));
+      
+      // Only load accessible links.
+      if (!$element->access->isAllowed()) {
+        continue;
+      }
+      
+      /** @var \Drupal\Core\Menu\MenuLinkInterface $link */
+      $link = $element->link;
+      if ($link) {
+        // The id consistes of plugin ID and link ID. 
+        $id = $link->getPluginId();
+        $this->overviewTree[$id]['id'] = $id;
+        if (!$link->isEnabled()) {
+          $this->overviewTree[$id]['title'] = '(' . $this->t('disabled') . ')' . $link->getTitle();
+        }
+        // @todo Remove this in https://www.drupal.org/node/2568785.
+        elseif ($id === 'user.logout') {
+          $this->overviewTree[$id]['title'] = ' (' . $this->t('<q>Log in</q> for anonymous users') . ')' . $link->getTitle();
+        }
+        // @todo Remove this in https://www.drupal.org/node/2568785.
+        elseif (($url = $link->getUrlObject()) && $url->isRouted() && $url->getRouteName() == 'user.page') {
+          $this->overviewTree[$id]['title'] = ' (' . $this->t('logged in users only') . ')' . $link->getTitle();
+        }
+        else {
+          $this->overviewTree[$id]['title'] = $link->getTitle();
+        }
+        
+        $this->overviewTree[$id]['parent'] = $link->getParent();
+        // Build the edit url.
+        // Allow for a custom edit link per plugin.
+        $edit_route = $link->getEditRoute();
+        if ($edit_route) {
+          $this->overviewTree[$id]['url'] = $edit_route->toString();
+        }
+        else {
+          // Fall back to the standard edit link.
+          $this->overviewTree[$id]['url'] = Url::fromRoute('menu_ui.link_edit', ['menu_link_plugin' => $link->getPluginId()])->toString();
+        }
+      }
+      
+      if ($element->subtree) {
+        $this->buildMenuLinkArray($element->subtree);
+      }
+    }
+    
+ /*    $tree_access_cacheability
+    ->merge(CacheableMetadata::createFromRenderArray($this->overviewTree))
+    ->applyTo($form); */
+    
+    return $this->overviewTree;
+  }
+}
+
diff --git a/src/Controller/HmTaxonomyController.php b/src/Controller/HmTaxonomyController.php
index e0992b1370923d25e9d02e02d2219d0e90757e0b..68cc884c34e87b50a28be4fdf4fdd4d5d809e0f2 100644
--- a/src/Controller/HmTaxonomyController.php
+++ b/src/Controller/HmTaxonomyController.php
@@ -30,31 +30,23 @@ class HmTaxonomyController extends ControllerBase {
    * @var \Drupal\taxonomy\TermStorageInterface
    */
   protected $storageController;
-
+  
   /**
-   * Display plugin manager.
+   * The hierarchy manager plugin type manager.
    *
-   * @var \Drupal\hierarchy_manager\Plugin\HmDisplayPluginInterface
+   * @var \Drupal\hierarchy_manager\PluginTypeManager
    */
-  protected $displayManager;
-
-  /**
-   * Setup plugin manager.
-   *
-   * @var \Drupal\hierarchy_manager\Plugin\HmSetupPluginManager
-   */
-  protected $setupManager;
+  protected $hmPluginTypeManager;
 
   /**
    * {@inheritdoc}
    */
-  public function __construct(CsrfTokenGenerator $csrfToken, EntityTypeManagerInterface $entity_type_manager, $display_manager, $setup_manager) {
+  public function __construct(CsrfTokenGenerator $csrfToken, EntityTypeManagerInterface $entity_type_manager, $plugin_type_manager) {
 
     $this->csrfToken = $csrfToken;
     $this->entityTypeManager = $entity_type_manager;
     $this->storageController = $entity_type_manager->getStorage('taxonomy_term');
-    $this->displayManager = $display_manager;
-    $this->setupManager = $setup_manager;
+    $this->hmPluginTypeManager = $plugin_type_manager;
   }
 
   /**
@@ -64,8 +56,7 @@ class HmTaxonomyController extends ControllerBase {
     return new static(
         $container->get('csrf_token'),
         $container->get('entity_type.manager'),
-        $container->get('plugin.manager.hm.display_plugin'),
-        $container->get('plugin.manager.hm.hmsetup')
+        $container->get('hm.plugin_type_manager')
         );
   }
 
@@ -99,31 +90,28 @@ class HmTaxonomyController extends ControllerBase {
       $tree = $this->storageController->loadTree($vid, $parent, $depth, TRUE);
 
       $access_control_handler = $this->entityTypeManager->getAccessControlHandler('taxonomy_term');
-
+      
       foreach ($tree as $term) {
         if ($term instanceof Term) {
           // User can only access the terms that they can update.
           if ($access_control_handler->access($term, 'update')) {
 
-            $term_array[] = [
-              'id' => $term->id(),
-              'text' => $term->label(),
-              'parent' => $term->parents[0],
-              'edit_url' => $term->toUrl('edit-form')->toString(),
-            ];
+            $term_array[] = $this->hmPluginTypeManager->buildHierarchyItem(
+                $term->id(), 
+                $term->label(), 
+                $term->parents[0], 
+                $term->toUrl('edit-form')->toString());
           }
         }
       }
     }
 
-    // Taxonomy setup plugin instance.
-    $taxonomy_setup_plugin = $this->setupManager->createInstance('hm_setup_taxonomy');
-    // Display profile.
-    $display_profile = $this->entityTypeManager->getStorage('hm_display_profile')->load($taxonomy_setup_plugin->getDispalyProfileId());
-    // Display plugin ID.
-    $display_plugin_id = $display_profile->get("plugin");
     // Display plugin instance.
-    $display_plugin = $this->displayManager->createInstance($display_plugin_id);
+    $display_plugin = $this->hmPluginTypeManager->getDisplayPluginInstance('hm_setup_taxonomy');
+    
+    if (empty($display_plugin)) {
+      return new JsonResponse(['result' => 'Display profile has not been set up.']);
+    }
 
     if (method_exists($display_plugin, 'treeData')) {
       // Convert the tree data to the structure
@@ -153,8 +141,10 @@ class HmTaxonomyController extends ControllerBase {
     }
 
     $target_position = $request->get('target');
-    $parent_id = intval($request->get('parent'));
+    $parent_id = $request->get('parent');
     $updated_terms = $request->get('keys');
+    //$after = $request->get('after');
+    $before = $request->get('before');
     $success = FALSE;
 
     if (is_array($updated_terms) && !empty($updated_terms)) {
@@ -175,36 +165,34 @@ class HmTaxonomyController extends ControllerBase {
       }
       else {
         // The parent term has children.
-        $target_position = intval($target_position);
-        $total = count($children);
-        // Move all terms after the target position forward.
-        if (isset($children[$target_position])) {
-          $weight = (int) $children[$target_position]->weight;
-          $tids = [];
-          $step = $weight + count($updated_terms);
-          for ($i = $target_position; $i < $total; $i++) {
-            if ($children[$i]->weight < $step++) {
-              $tids[] = $children[$i]->tid;
-            }
-            else {
-              // There is planty room, no need to move anymore.
-              break;
-            }
+        $target_position = intval($target_position);       
+        $all_siblings = [];
+        $insert_after = TRUE;
+        $position = 0;
+        
+        foreach ($children as $child) {
+          // Figure out if the new links are inserted
+          // after the target position.
+          if ($position++ == $target_position && $child->tid !== $before) {
+            $insert_after = FALSE;
           }
-          $step = $weight + count($updated_terms);
+          
+          $all_siblings[$child->tid] = (int) $child->weight;
+        }
+        
+        $new_hierarchy = $this->hmPluginTypeManager->updateHierarchy($target_position, $all_siblings, $updated_terms, $insert_after);
+        $weight = $new_hierarchy['start_weight'];
+        $moving_siblings = $new_hierarchy['moving_siblings'];
+        $tids = array_keys($moving_siblings);
+        
+        if (!empty($tids)) {
+          // Update siblings.
           $term_siblings = Term::loadMultiple($tids);
           foreach ($term_siblings as $term) {
-            $term->setWeight($step++);
+            $term->setWeight($moving_siblings[$term->id()]);
             $success = $term->save();
           }
         }
-        elseif ($target_position === $total) {
-          // Insert into the end.
-          $weight = intval(array_slice($children, -1)[0]->weight) + 1;
-        }
-        else {
-          return new JsonResponse(['result' => 'The term is not found.']);
-        }
       }
       
       // Load all terms needed to update.
diff --git a/src/Form/HmMenuForm.php b/src/Form/HmMenuForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..0476df8abe386d0fd624bffd5540aeb03245b6a8
--- /dev/null
+++ b/src/Form/HmMenuForm.php
@@ -0,0 +1,145 @@
+<?php
+
+namespace Drupal\hierarchy_manager\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\menu_ui\MenuForm;
+
+class HmMenuForm extends MenuForm {
+  
+  /**
+   * The indicator if the menu hierarchy manager is enabled.
+   * 
+   * @var bool|NULL
+   */
+  private $isEnabled = NULL;
+  
+  /**
+   * The hierarchy manager plugin type manager.
+   *
+   * @var \Drupal\hierarchy_manager\PluginTypeManager
+   */
+  private $hmPluginTypeManager = NULL;
+  
+  /**
+   * {@inheritdoc}
+   */
+  public function form(array $form, FormStateInterface $form_state) {
+    $form = parent::form($form, $form_state);
+    
+    // If the menu hierarchy manager plugin is enabled.
+    // Override the menu overview form.
+    if ($this->isMenuPluginEnabled() && $this->loadPluginManager()) {
+          $menu = $this->entity;
+          
+          // Add menu links administration form for existing menus.
+          if (!$menu->isNew() || $menu->isLocked()) {
+            // We are removing the menu link overview form
+            // and using our own hierarchy manager tree instead.
+            // The overview form implemented by Drupal Menu UI module
+            // @see \Drupal\menu_ui\MenuForm::form()
+            unset($form['links']);
+            $form['hm_links'] = $this->buildOverviewTree([], $form_state);
+          }
+    }
+    
+    return $form;
+  }
+  
+  /**
+   * Submit handler for the menu overview form.
+   *
+   * The hierarchy manager tree is a pure front-end solution in which
+   * we don't need to deal with the submission data from the back-end.
+   * Therefore nothing need to do,
+   * if the menu hierarchy plugin is enabled.
+   */
+  protected function submitOverviewForm(array $complete_form, FormStateInterface $form_state) {
+    if (!$this->isMenuPluginEnabled()) {
+      parent::submitOverviewForm($complete_form, $form_state);
+    }
+  }
+  
+  /**
+   * Build a menu links overview tree element.
+   * 
+   * @param array $form
+   *   Parent form array.
+   * @param FormStateInterface $form_state
+   *   Form state object.
+   * @return NULL|array
+   */
+  protected function buildOverviewTree(array $form, FormStateInterface $form_state) {
+    global $base_path;
+
+    $display_plugin_instance = $this->hmPluginTypeManager->getDisplayPluginInstance('hm_setup_menu');
+    
+    if (!empty($display_plugin_instance)) {
+      if (method_exists($display_plugin_instance, 'getForm')) {
+        // Menu ID.
+        $mid = $this->entity->id();
+        // CSRF token.
+        $token = \Drupal::csrfToken()->get($mid);
+        // Get current language.
+        $language = \Drupal::languageManager()->getCurrentLanguage();
+        // Destination for edit link.
+        $destination = $this->getDestinationArray();
+        if (isset($destination['destination'])) {
+          $destination = $destination['destination'];
+        }
+        else {
+          $destination = '';
+        }
+        if ($language->isDefault()) {
+          $source_url = $base_path . 'admin/hierarchy_manager/menu/json/' . $mid . '?token=' . $token . '&destination=' . $destination;
+          $update_url = $base_path . 'admin/hierarchy_manager/menu/update/' . $mid . '?token=' . $token;
+        }
+        else {
+          $source_url = $base_path . $language->getId() . '/admin/hierarchy_manager/menu/json/' . $mid . '?token=' . $token . '&destination=' . $destination;
+          $update_url = $base_path . $language->getId() . '/admin/hierarchy_manager/menu/update/' . $mid . '?token=' . $token;
+        }
+        return $display_plugin_instance->getForm($source_url, $update_url, $form, $form_state);
+      }
+    }
+    
+    return [];
+  }
+  
+  /**
+   * Create a hierarchy manager plugin manager.
+   * 
+   * @return \Drupal\hierarchy_manager\PluginTypeManager
+   */
+  protected function loadPluginManager() {
+    if (empty($this->hmPluginTypeManager)) {
+      $this->hmPluginTypeManager = \Drupal::service('hm.plugin_type_manager');
+    }
+    
+    return $this->hmPluginTypeManager;
+  }
+  
+  /**
+   * Check if the menu hierarchy plugin is enabled.
+   * 
+   * @return boolean|NULL
+   *   Return TRUE if the menu plugin is enabled,
+   *   otherwise return FALSE.
+   */
+  protected function isMenuPluginEnabled() {
+    if ($this->isEnabled === NULL) {
+      if ($config = \Drupal::config('hierarchy_manager.hmconfig')) {
+        if ($allowed_setup_plugins = $config->get('allowed_setup_plugins')) {
+          if (!empty($allowed_setup_plugins['hm_setup_menu'])) {
+            $this->isEnabled = TRUE;
+          }
+          else {
+            $this->isEnabled = FALSE;
+          }
+        }
+      }
+    }
+    
+    return $this->isEnabled;
+  }
+}
+
diff --git a/src/Plugin/HmDisplayPlugin/HmDisplayJstree.php b/src/Plugin/HmDisplayPlugin/HmDisplayJstree.php
index c5588bac45800235050b67b09ec38f88d5845c04..ea26592bfb8283c613ac4030952cae20f9c2ca63 100644
--- a/src/Plugin/HmDisplayPlugin/HmDisplayJstree.php
+++ b/src/Plugin/HmDisplayPlugin/HmDisplayJstree.php
@@ -85,7 +85,7 @@ class HmDisplayJstree extends HmDisplayPluginBase implements HmDisplayPluginInte
     foreach ($data as $tree_node) {
       $jstree_node = $tree_node;
       // The root id for jsTree is #.
-      if ($tree_node['parent'] === '0') {
+      if (empty($tree_node['parent'])) {
         $jstree_node['parent'] = '#';
       }
       // Custom data
diff --git a/src/Plugin/HmSetupPlugin/HmMenu.php b/src/Plugin/HmSetupPlugin/HmMenu.php
new file mode 100644
index 0000000000000000000000000000000000000000..6feb0e7eb6ac7eaa94040ca14a4b602b97844462
--- /dev/null
+++ b/src/Plugin/HmSetupPlugin/HmMenu.php
@@ -0,0 +1,18 @@
+<?php
+
+namespace DRupal\hierarchy_manager\Plugin\HmSetupPlugin;
+
+use Drupal\hierarchy_manager\Plugin\HmSetupPluginInterface;
+use Drupal\hierarchy_manager\Plugin\HmSetupPluginBase;
+
+/**
+ * Menu link hierarchy setup plugin.
+ *
+ * @HmSetupPlugin(
+ *   id = "hm_setup_menu",
+ *   label = @Translation("Menu link hierarchy setup plugin")
+ * )
+ */
+class HmMenu extends HmSetupPluginBase implements HmSetupPluginInterface {
+}
+
diff --git a/src/Plugin/HmSetupPluginBase.php b/src/Plugin/HmSetupPluginBase.php
index 84b0558374987d72903802ab7f9bceb88e70096b..09992080022de73e6202000dcef2ce98e0ec8db3 100644
--- a/src/Plugin/HmSetupPluginBase.php
+++ b/src/Plugin/HmSetupPluginBase.php
@@ -32,8 +32,12 @@ abstract class HmSetupPluginBase extends PluginBase implements HmSetupPluginInte
     parent::__construct($configuration, $plugin_id, $plugin_definition);
 
     $plugin_settings = \Drupal::config('hierarchy_manager.hmconfig')->get('setup_plugin_settings');
-    $settings = $plugin_settings[$this->pluginId] ?: [];
-    $this->displayProfile = $settings['display_profile'];
+    if (isset($plugin_settings[$this->pluginId])) {
+      $this->displayProfile = $plugin_settings[$this->pluginId]['display_profile'];
+    }
+    else {
+      $this->displayProfile = '';
+    }
   }
 
   /**
diff --git a/src/PluginTypeManager.php b/src/PluginTypeManager.php
new file mode 100644
index 0000000000000000000000000000000000000000..a6a5a8b3d0f6b5fe23a6b0430ff17ea10f72ca8b
--- /dev/null
+++ b/src/PluginTypeManager.php
@@ -0,0 +1,203 @@
+<?php
+
+namespace Drupal\hierarchy_manager;
+
+use Drupal\Core\Entity\EntityTypeManagerInterface;
+use Drupal\hierarchy_manager\Plugin\HmDisplayPluginManager;
+use Drupal\hierarchy_manager\Plugin\HmSetupPluginManager;
+
+class PluginTypeManager {
+  
+  /**
+   * Display plugin manager.
+   *
+   * @var \Drupal\hierarchy_manager\Plugin\HmDisplayPluginManager
+   */
+  protected $displayManager;
+  
+  /**
+   * Setup plugin manager.
+   *
+   * @var \Drupal\hierarchy_manager\Plugin\HmSetupPluginManager
+   */
+  protected $setupManager;
+  
+  /**
+   * The entity type manager.
+   *
+   * @var \Drupal\Core\Entity\EntityTypeManagerInterface
+   */
+  protected $entityTypeManager;
+  
+  /**
+   * {@inheritdoc}
+   */
+  public function __construct(EntityTypeManagerInterface $entity_type_manager, HmDisplayPluginManager $display_manager, HmSetupPluginManager $setup_manager) {
+    $this->entityTypeManager = $entity_type_manager;
+    $this->displayManager = $display_manager;
+    $this->setupManager = $setup_manager;
+  }
+  
+  /**
+   * Construct an item inside the hierarchy.
+   * 
+   * @param string|int $id
+   *   Item id.
+   * @param string $label
+   *   Item text.
+   * @param string $parent
+   *   Parent id of the item.
+   * @param string $edit_url
+   *   The URL where to edit this item.
+   * @return array
+   *   The hierarchy item array.
+   */
+  public function buildHierarchyItem($id, $label, $parent, $edit_url) {
+    return 
+    [
+    'id' => $id,
+    'text' => $label,
+    'parent' => $parent,
+    'edit_url' => $edit_url,
+    ];
+  }
+  
+  /**
+   * Get a display plugin instance according to a setup plugin.
+   * 
+   * @param string $setup_plugin_id
+   *   setup plugin ID.
+   * @return NULL|object
+   *   The display plugin instance.
+   */
+  public function getDisplayPluginInstance(string $setup_plugin_id) {
+    // The setup plugin instance.
+    $setup_plugin = $this->setupManager->createInstance($setup_plugin_id);
+    // Display profile.
+    $display_profile = $this->entityTypeManager->getStorage('hm_display_profile')->load($setup_plugin->getDispalyProfileId());
+    
+    if (empty($display_profile)) {
+      return NULL;
+    }
+    
+    // Display plugin ID.
+    $display_plugin_id = $display_profile->get("plugin");
+    
+    return $this->displayManager->createInstance($display_plugin_id);
+  }
+  
+  /**
+   * Update the items for a hierarchy
+   * 
+   * @param int $target_position
+   *   Which position the new items will be insert.
+   * @param array $all_siblings
+   *   All siblings of the new items in an array[$item_id => (int)$weight]
+   * @param array $updated_items
+   *   IDs of new items inserted.
+   * @param int|bool $after
+   *   Indicator if new items are inserted after target position.
+   *   
+   * @return array
+   *   All siblings needed to move and their new weights.
+   */
+  public function updateHierarchy(int $target_position, array $all_siblings, array $updated_items, $after) {
+    $filtered_moving_siblings = [];
+    $first_half = TRUE;
+
+    $total = count($all_siblings);
+    if ($target_position === 0) {
+      // The insert postion is the first position.
+      // we don't need to move any siblings.
+      $weight = (int) reset($all_siblings) - 1;
+    }   
+    elseif ($target_position >= $total - 1) {
+      // The insert postion is the end,
+      // we don't need to move any siblings.
+      $last_item= array_slice($all_siblings, -1, 1, TRUE);
+      $weight = (int) reset($last_item) + 1;
+    }
+    else {
+      $target_item = array_slice($all_siblings, $target_position, 1, TRUE);
+      $weight = (int) reset($target_item);
+      // If the target position is in the second half,
+      // we will move all siblings
+      // after the target position forward.
+      // Otherwise, we will move siblings
+      // before the target position backwards.
+      if ($target_position >= $total / 2) {
+        $first_half = FALSE;
+        
+        if ($after) {
+          // Insert after the target position.
+          // The target stay where it is.
+          $weight += 1;
+          $moving_siblings = array_slice($all_siblings, $target_position + 1, NULL, TRUE);
+        }
+        else {
+          // Insert before the target position.
+          // The target need to move forwards.
+          $moving_siblings = array_slice($all_siblings, $target_position, NULL, TRUE);
+        }
+        $step = $weight + count($updated_items);
+      }
+      else {
+        if ($after) {
+          // Insert after the target position.
+          // The target need to move backwards.
+          $moving_siblings = array_slice($all_siblings, 0, $target_position + 1, TRUE);
+        }
+        else {
+          // Insert before the target position.
+          // The target stay where it is.
+          $weight -= 1;
+          $moving_siblings = array_slice($all_siblings, 0, $target_position, TRUE);
+        }
+        $weight = $step = $weight - count($updated_items);
+        // Reverse the siblings_moved array
+        // as we will decrease the weight
+        // starting from the first element
+        // and the new weight should be in
+        // an opposite order.
+        $moving_siblings = array_reverse($moving_siblings, TRUE);
+      }
+      
+      // Move all siblings that need to move.
+      foreach($moving_siblings as $item_id => $item_weight) {
+        // Skip all links in the updated array. They will be moved later.
+        if (in_array($item_id, $updated_items)) {
+          continue;
+        }
+        if ($first_half) {
+          // While moving the first half of the siblings,
+          // all moving siblings' weight are decreased,
+          // if they are greater than the step.
+          if ((int)$item_weight < --$step) {
+            // There is planty room, no need to move anymore.
+            break;
+          }
+          else {
+            // Update the weight.
+            $filtered_moving_siblings[$item_id] = $step;
+          }
+        }
+        else {
+          // While moving the second half of the siblings,
+          // all moving siblings' weight are increased,
+          // if they are less than the step.
+          if ((int)$item_weight < ++$step) {
+            // Update the weight.
+            $filtered_moving_siblings[$item_id] = $step;
+          }
+          else {
+            // There is planty room, no need to move anymore.
+            break;
+          }
+        }
+      }
+    }
+    
+    return ['start_weight' => $weight, 'moving_siblings' => $filtered_moving_siblings];
+  }
+}
+