diff --git a/core/modules/block/src/Tests/Migrate/d6/MigrateBlockTest.php b/core/modules/block/src/Tests/Migrate/d6/MigrateBlockTest.php
index 232fa4a..28147dc 100644
--- a/core/modules/block/src/Tests/Migrate/d6/MigrateBlockTest.php
+++ b/core/modules/block/src/Tests/Migrate/d6/MigrateBlockTest.php
@@ -7,8 +7,10 @@
 
 namespace Drupal\block\Tests\Migrate\d6;
 
+use Drupal\migrate\MigrateExecutable;
 use Drupal\block\Entity\Block;
 use Drupal\migrate_drupal\Tests\d6\MigrateDrupal6TestBase;
+use Drupal\systeml\Plugin\migrate\source\Menu;
 
 /**
  * Upgrade block settings to block.block.*.yml.
diff --git a/core/modules/menu_link_content/migration_templates/d6_menu_links.yml b/core/modules/menu_link_content/migration_templates/d6_menu_links.yml
index 4ee210b..ac4743a 100644
--- a/core/modules/menu_link_content/migration_templates/d6_menu_links.yml
+++ b/core/modules/menu_link_content/migration_templates/d6_menu_links.yml
@@ -3,7 +3,7 @@ label: Drupal 6 menu links
 migration_tags:
   - Drupal 6
 source:
-  plugin: d6_menu_link
+  plugin: menu_link
   constants:
     bundle: menu_link_content
 process:
diff --git a/core/modules/menu_link_content/migration_templates/d7_menu_links.yml b/core/modules/menu_link_content/migration_templates/d7_menu_links.yml
new file mode 100644
index 0000000..4ea6ca1
--- /dev/null
+++ b/core/modules/menu_link_content/migration_templates/d7_menu_links.yml
@@ -0,0 +1,56 @@
+id: d7_menu_links
+label: Drupal 7 menu links
+migration_tags:
+  - Drupal 7
+source:
+  plugin: menu_link
+  constants:
+    bundle: menu_link_content
+process:
+  id: mlid
+  bundle: 'constants/bundle'
+  title: link_title
+  description:
+    plugin: extract
+    source:
+      - options
+    index:
+      - 0
+      - attributes
+      - title
+  menu_name:
+    plugin: migration
+    migration: d7_menu
+    source: menu_name
+  'link/uri':
+    plugin: internal_uri
+    source:
+      - link_path
+  'link/options': options
+  route:
+    plugin: route
+    source:
+      - link_path
+      - options
+  route_name: @route/route_name
+  route_parameters: @route/route_parameters
+  url: @route/url
+  options: @route/options
+  external: external
+  weight: weight
+  expanded: expanded
+  enabled: enabled
+  parent:
+    -
+      plugin: skip_on_empty
+      method: process
+      source: plid
+    -
+      plugin: migration
+      migration: d7_menu_links
+  changed: updated
+destination:
+  plugin: entity:menu_link_content
+migration_dependencies:
+  required:
+    - d7_menu
diff --git a/core/modules/menu_link_content/src/Plugin/migrate/source/MenuLink.php b/core/modules/menu_link_content/src/Plugin/migrate/source/MenuLink.php
new file mode 100644
index 0000000..87afa8f
--- /dev/null
+++ b/core/modules/menu_link_content/src/Plugin/migrate/source/MenuLink.php
@@ -0,0 +1,110 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Plugin\migrate\source\MenuLink.
+ */
+
+namespace Drupal\menu_link_content\Plugin\migrate\source;
+
+use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
+use Drupal\migrate\Row;
+
+/**
+ * Drupal menu link source from database.
+ *
+ * @MigrateSource(
+ *   id = "menu_link",
+ * )
+ */
+class MenuLink extends DrupalSqlBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    $query = $this->select('menu_links', 'ml')
+      ->fields('ml', array(
+        'menu_name',
+        'mlid',
+        'plid',
+        'link_path',
+        'router_path',
+        'link_title',
+        'options',
+        'module',
+        'hidden',
+        'external',
+        'has_children',
+        'expanded',
+        'weight',
+        'depth',
+        'customized',
+        'p1',
+        'p2',
+        'p3',
+        'p4',
+        'p5',
+        'p6',
+        'p7',
+        'p8',
+        'p9',
+        'updated'
+      ))
+      ->condition('module', 'menu')
+      ->condition('customized', 1);
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    return array(
+      'menu_name' => t("The menu name. All links with the same menu name (such as 'navigation') are part of the same menu."),
+      'mlid' => t('The menu link ID (mlid) is the integer primary key.'),
+      'plid' => t('The parent link ID (plid) is the mlid of the link above in the hierarchy, or zero if the link is at the top level in its menu.'),
+      'link_path' => t('The Drupal path or external path this link points to.'),
+      'router_path' => t('For links corresponding to a Drupal path (external = 0), this connects the link to a {menu_router}.path for joins.'),
+      'link_title' => t('The text displayed for the link, which may be modified by a title callback stored in {menu_router}.'),
+      'options' => t('A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.'),
+      'module' => t('The name of the module that generated this link.'),
+      'hidden' => t('A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link)'),
+      'external' => t('A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).'),
+      'has_children' => t('Flag indicating whether any links have this link as a parent (1 = children exist, 0 = no children).'),
+      'expanded' => 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)'),
+      'weight' => t('Link weight among links in the same menu at the same depth.'),
+      'depth' => t('The depth relative to the top level. A link with plid == 0 will have depth == 1.'),
+      'customized' => t('A flag to indicate that the user has manually created or edited the link (1 = customized, 0 = not customized).'),
+      'p1' => t('The first mlid in the materialized path. If N = depth, then pN must equal the mlid. If depth > 1 then p(N-1) must equal the plid. All pX where X > depth must equal zero. The columns p1 .. p9 are also called the parents.'),
+      'p2' => t('The second mlid in the materialized path. See p1.'),
+      'p3' => t('The third mlid in the materialized path. See p1.'),
+      'p4' => t('The fourth mlid in the materialized path. See p1.'),
+      'p5' => t('The fifth mlid in the materialized path. See p1.'),
+      'p6' => t('The sixth mlid in the materialized path. See p1.'),
+      'p7' => t('The seventh mlid in the materialized path. See p1.'),
+      'p8' => t('The eighth mlid in the materialized path. See p1.'),
+      'p9' => t('The ninth mlid in the materialized path. See p1.'),
+      'updated' => t('Flag that indicates that this link was generated during the update from Drupal 5.'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareRow(Row $row) {
+    $row->setSourceProperty('options', unserialize($row->getSourceProperty('options')));
+    $row->setSourceProperty('enabled', !$row->getSourceProperty('hidden'));
+
+    return parent::prepareRow($row);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    $ids['mlid']['type'] = 'integer';
+    return $ids;
+  }
+
+}
diff --git a/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLink.php b/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLink.php
deleted file mode 100644
index 8e285ad..0000000
--- a/core/modules/menu_link_content/src/Plugin/migrate/source/d6/MenuLink.php
+++ /dev/null
@@ -1,110 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\menu_link_content\Plugin\migrate\source\d6\MenuLink.
- */
-
-namespace Drupal\menu_link_content\Plugin\migrate\source\d6;
-
-use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
-use Drupal\migrate\Row;
-
-/**
- * Drupal 6 menu link source from database.
- *
- * @MigrateSource(
- *   id = "d6_menu_link",
- * )
- */
-class MenuLink extends DrupalSqlBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function query() {
-    $query = $this->select('menu_links', 'ml')
-      ->fields('ml', array(
-        'menu_name',
-        'mlid',
-        'plid',
-        'link_path',
-        'router_path',
-        'link_title',
-        'options',
-        'module',
-        'hidden',
-        'external',
-        'has_children',
-        'expanded',
-        'weight',
-        'depth',
-        'customized',
-        'p1',
-        'p2',
-        'p3',
-        'p4',
-        'p5',
-        'p6',
-        'p7',
-        'p8',
-        'p9',
-        'updated'
-      ))
-      ->condition('module', 'menu')
-      ->condition('customized', 1);
-    return $query;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fields() {
-    return array(
-      'menu_name' => t("The menu name. All links with the same menu name (such as 'navigation') are part of the same menu."),
-      'mlid' => t('The menu link ID (mlid) is the integer primary key.'),
-      'plid' => t('The parent link ID (plid) is the mlid of the link above in the hierarchy, or zero if the link is at the top level in its menu.'),
-      'link_path' => t('The Drupal path or external path this link points to.'),
-      'router_path' => t('For links corresponding to a Drupal path (external = 0), this connects the link to a {menu_router}.path for joins.'),
-      'link_title' => t('The text displayed for the link, which may be modified by a title callback stored in {menu_router}.'),
-      'options' => t('A serialized array of options to be passed to the url() or l() function, such as a query string or HTML attributes.'),
-      'module' => t('The name of the module that generated this link.'),
-      'hidden' => t('A flag for whether the link should be rendered in menus. (1 = a disabled menu item that may be shown on admin screens, -1 = a menu callback, 0 = a normal, visible link)'),
-      'external' => t('A flag to indicate if the link points to a full URL starting with a protocol, like http:// (1 = external, 0 = internal).'),
-      'has_children' => t('Flag indicating whether any links have this link as a parent (1 = children exist, 0 = no children).'),
-      'expanded' => 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)'),
-      'weight' => t('Link weight among links in the same menu at the same depth.'),
-      'depth' => t('The depth relative to the top level. A link with plid == 0 will have depth == 1.'),
-      'customized' => t('A flag to indicate that the user has manually created or edited the link (1 = customized, 0 = not customized).'),
-      'p1' => t('The first mlid in the materialized path. If N = depth, then pN must equal the mlid. If depth > 1 then p(N-1) must equal the plid. All pX where X > depth must equal zero. The columns p1 .. p9 are also called the parents.'),
-      'p2' => t('The second mlid in the materialized path. See p1.'),
-      'p3' => t('The third mlid in the materialized path. See p1.'),
-      'p4' => t('The fourth mlid in the materialized path. See p1.'),
-      'p5' => t('The fifth mlid in the materialized path. See p1.'),
-      'p6' => t('The sixth mlid in the materialized path. See p1.'),
-      'p7' => t('The seventh mlid in the materialized path. See p1.'),
-      'p8' => t('The eighth mlid in the materialized path. See p1.'),
-      'p9' => t('The ninth mlid in the materialized path. See p1.'),
-      'updated' => t('Flag that indicates that this link was generated during the update from Drupal 5.'),
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function prepareRow(Row $row) {
-    $row->setSourceProperty('options', unserialize($row->getSourceProperty('options')));
-    $row->setSourceProperty('enabled', !$row->getSourceProperty('hidden'));
-
-    return parent::prepareRow($row);
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getIds() {
-    $ids['mlid']['type'] = 'integer';
-    return $ids;
-  }
-
-}
diff --git a/core/modules/menu_link_content/src/Tests/Migrate/d6/MigrateMenuLinkTest.php b/core/modules/menu_link_content/src/Tests/Migrate/d6/MigrateMenuLinkTest.php
index cf21ebd..3a13bc6 100644
--- a/core/modules/menu_link_content/src/Tests/Migrate/d6/MigrateMenuLinkTest.php
+++ b/core/modules/menu_link_content/src/Tests/Migrate/d6/MigrateMenuLinkTest.php
@@ -63,7 +63,7 @@ public function testMenuLinks() {
     $this->assertIdentical('Test menu link 2', $menu_link->getDescription());
     $this->assertIdentical(TRUE, $menu_link->isEnabled());
     $this->assertIdentical(TRUE, $menu_link->isExpanded());
-    $this->assertIdentical(['query' => 'foo=bar', 'attributes' => ['title' => 'Test menu link 2']], $menu_link->link->options);
+    $this->assertIdentical(['attributes' => ['title' => 'Test menu link 2'], 'query' => 'foo=bar'], $menu_link->link->options);
     $this->assertIdentical('internal:/admin', $menu_link->link->uri);
     $this->assertIdentical(12, $menu_link->getWeight());
 
diff --git a/core/modules/menu_link_content/src/Tests/Migrate/d7/MigrateMenuLinkTest.php b/core/modules/menu_link_content/src/Tests/Migrate/d7/MigrateMenuLinkTest.php
new file mode 100644
index 0000000..0caf170
--- /dev/null
+++ b/core/modules/menu_link_content/src/Tests/Migrate/d7/MigrateMenuLinkTest.php
@@ -0,0 +1,82 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Tests\Migrate\d7\MigrateMenuLinkTest.
+ */
+
+namespace Drupal\menu_link_content\Tests\Migrate\d7;
+
+use Drupal\menu_link_content\Entity\MenuLinkContent;
+use Drupal\menu_link_content\MenuLinkContentInterface;
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
+
+/**
+ * Menu link migration.
+ *
+ * @group menu_link_content
+ */
+class MigrateMenuLinkTest extends MigrateDrupal7TestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('link', 'menu_ui', 'menu_link_content');
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->installSchema('system', ['router']);
+    $this->installEntitySchema('menu_link_content');
+
+    $menu = entity_create('menu', array('id' => 'menu-test-menu'));
+    $menu->enforceIsNew(TRUE);
+    $menu->save();
+
+    $this->prepareMigrations(array(
+      'd7_menu' => array(
+        array(array('menu-test-menu'), array('menu-test-menu')),
+      ),
+    ));
+    /*
+    $migration = entity_load('migration', 'd7_menu_links');
+    $dumps = array(
+      $this->getDumpDirectory() . '/MenuLinks.php',
+    );
+    $this->prepare($migration, $dumps);
+    $executable = new MigrateExecutable($migration, $this);
+    $executable->import();
+    */
+    $this->loadDumps(['MenuLinks.php']);
+    $this->executeMigration('d7_menu_links');
+  }
+
+  protected function assertEntity($id, $title, $menu, $description, $enabled, $expanded, array $attributes, $uri, $weight) {
+    /** @var \Drupal\menu_link_content\MenuLinkContentInterface $menu_link */
+    $menu_link = MenuLinkContent::load($id);
+    $this->assertTrue($menu_link instanceof MenuLinkContentInterface);
+    $this->assertIdentical($title, $menu_link->getTitle());
+    $this->assertIdentical($menu, $menu_link->getMenuName());
+    // The migration sets the description of the link to the value of the
+    // 'title' attribute. Bit strange, but there you go.
+    $this->assertIdentical($description, $menu_link->getDescription());
+    $this->assertIdentical($enabled, $menu_link->isEnabled());
+    $this->assertIdentical($expanded, $menu_link->isExpanded());
+    $this->assertIdentical($attributes, $menu_link->link->options);
+    $this->assertIdentical($uri, $menu_link->link->uri);
+    $this->assertIdentical($weight, $menu_link->getWeight());
+  }
+
+  public function testMenuLinks() {
+    $this->assertEntity(467, 'Google', 'menu-test-menu', 'Google', TRUE, FALSE, ['attributes' => ['title' => 'Google']], 'http://google.com', 0);
+    $this->assertEntity(468, 'Yahoo', 'menu-test-menu', 'Yahoo', TRUE, FALSE, ['attributes' => ['title' => 'Yahoo']], 'http://yahoo.com', 0);
+    $this->assertEntity(469, 'Bing', 'menu-test-menu', 'Bing', TRUE, FALSE, ['attributes' => ['title' => 'Bing']], 'http://bing.com', 0);
+  }
+
+}
diff --git a/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/source/MenuLinkSourceTest.php b/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/source/MenuLinkSourceTest.php
new file mode 100644
index 0000000..e4b2382
--- /dev/null
+++ b/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/source/MenuLinkSourceTest.php
@@ -0,0 +1,132 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\menu_link_content\Unit\Plugin\migrate\source\MenuLinkSourceTest.
+ */
+namespace Drupal\Tests\menu_link_content\Unit\Plugin\migrate\source;
+
+use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
+
+/**
+ * Tests menu link source plugin.
+ *
+ * @group menu_link_content
+ */
+class MenuLinkSourceTest extends MigrateSqlSourceTestCase {
+
+  // The plugin system is not working during unit testing so the source plugin
+  // class needs to be manually specified.
+  const PLUGIN_CLASS = 'Drupal\menu_link_content\Plugin\migrate\source\MenuLink';
+
+  // The fake Migration configuration entity.
+  protected $migrationConfiguration = array(
+    // The ID of the entity, can be any string.
+    'id' => 'mlid',
+    // Leave it empty for now.
+    'idlist' => array(),
+    // This needs to be the identifier of the actual key: cid for comment, nid
+    // for node and so on.
+    'source' => array(
+      'plugin' => 'menu_link',
+    ),
+    'sourceIds' => array(
+      'mlid' => array(
+        // This is where the field schema would go but for now we need to
+        // specify the table alias for the key. Most likely this will be the
+        // same as BASE_ALIAS.
+        'alias' => 'ml',
+      ),
+    ),
+    'destinationIds' => array(
+      'mlid' => array(
+        // This is where the field schema would go.
+      ),
+    ),
+  );
+
+  protected $expectedResults = array(
+    array(
+      'menu_name' => 'menu-test-menu',
+      'mlid' => 138,
+      'plid' => 0,
+      'link_path' => 'admin',
+      'router_path' => 'admin',
+      'link_title' => 'Test 1',
+      'options' => array('attributes' => array('title' => 'Test menu link 1')),
+      'module' => 'menu',
+      'hidden' => 0,
+      'external' => 0,
+      'has_children' => 1,
+      'expanded' => 0,
+      'weight' => 15,
+      'depth' => 1,
+      'customized' => 1,
+      'p1' => '138',
+      'p2' => '0',
+      'p3' => '0',
+      'p4' => '0',
+      'p5' => '0',
+      'p6' => '0',
+      'p7' => '0',
+      'p8' => '0',
+      'p9' => '0',
+      'updated' => '0',
+    ),
+    array(
+      'menu_name' => 'menu-test-menu',
+      'mlid' => 139,
+      'plid' => 138,
+      'link_path' => 'admin/modules',
+      'router_path' => 'admin/modules',
+      'link_title' => 'Test 2',
+      'options' => array('attributes' => array('title' => 'Test menu link 2')),
+      'module' => 'menu',
+      'hidden' => 0,
+      'external' => 0,
+      'has_children' => 0,
+      'expanded' => 0,
+      'weight' => 12,
+      'depth' => 2,
+      'customized' => 1,
+      'p1' => '138',
+      'p2' => '139',
+      'p3' => '0',
+      'p4' => '0',
+      'p5' => '0',
+      'p6' => '0',
+      'p7' => '0',
+      'p8' => '0',
+      'p9' => '0',
+      'updated' => '0',
+    ),
+  );
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    // This array stores the database.
+    foreach ($this->expectedResults as $k => $row) {
+      $this->databaseContents['menu_links'][$k] = $row;
+      $this->databaseContents['menu_links'][$k]['options'] = serialize($this->databaseContents['menu_links'][$k]['options']);
+    }
+    parent::setUp();
+  }
+
+}
+
+namespace Drupal\Tests\menu_link_content\Unit\Plugin\migrate\source;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\menu_link_content\Plugin\migrate\source\MenuLink;
+
+class TestMenuLink extends MenuLink {
+  public function setDatabase(Connection $database) {
+    $this->database = $database;
+  }
+  public function setModuleHandler(ModuleHandlerInterface $module_handler) {
+    $this->moduleHandler = $module_handler;
+  }
+}
diff --git a/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/source/d6/MenuLinkSourceTest.php b/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/source/d6/MenuLinkSourceTest.php
deleted file mode 100644
index e641c09..0000000
--- a/core/modules/menu_link_content/tests/src/Unit/Plugin/migrate/source/d6/MenuLinkSourceTest.php
+++ /dev/null
@@ -1,118 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Tests\menu_link_content\Unit\Plugin\migrate\source\d6\MenuLinkSourceTest.
- */
-
-namespace Drupal\Tests\menu_link_content\Unit\Plugin\migrate\source\d6;
-
-use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
-
-/**
- * Tests D6 menu link source plugin.
- *
- * @group menu_link_content
- */
-class MenuLinkSourceTest extends MigrateSqlSourceTestCase {
-
-  // The plugin system is not working during unit testing so the source plugin
-  // class needs to be manually specified.
-  const PLUGIN_CLASS = 'Drupal\menu_link_content\Plugin\migrate\source\d6\MenuLink';
-
-  // The fake Migration configuration entity.
-  protected $migrationConfiguration = array(
-    // The ID of the entity, can be any string.
-    'id' => 'mlid',
-    // Leave it empty for now.
-    'idlist' => array(),
-    // This needs to be the identifier of the actual key: cid for comment, nid
-    // for node and so on.
-    'source' => array(
-      'plugin' => 'drupal6_menu_link',
-    ),
-    'sourceIds' => array(
-      'mlid' => array(
-        // This is where the field schema would go but for now we need to
-        // specify the table alias for the key. Most likely this will be the
-        // same as BASE_ALIAS.
-        'alias' => 'ml',
-      ),
-    ),
-    'destinationIds' => array(
-      'mlid' => array(
-        // This is where the field schema would go.
-      ),
-    ),
-  );
-
-  protected $expectedResults = array(
-    array(
-      'menu_name' => 'menu-test-menu',
-      'mlid' => 138,
-      'plid' => 0,
-      'link_path' => 'admin',
-      'router_path' => 'admin',
-      'link_title' => 'Test 1',
-      'options' => array('attributes' => array('title' => 'Test menu link 1')),
-      'module' => 'menu',
-      'hidden' => 0,
-      'external' => 0,
-      'has_children' => 1,
-      'expanded' => 0,
-      'weight' => 15,
-      'depth' => 1,
-      'customized' => 1,
-      'p1' => '138',
-      'p2' => '0',
-      'p3' => '0',
-      'p4' => '0',
-      'p5' => '0',
-      'p6' => '0',
-      'p7' => '0',
-      'p8' => '0',
-      'p9' => '0',
-      'updated' => '0',
-    ),
-    array(
-      'menu_name' => 'menu-test-menu',
-      'mlid' => 139,
-      'plid' => 138,
-      'link_path' => 'admin/modules',
-      'router_path' => 'admin/modules',
-      'link_title' => 'Test 2',
-      'options' => array('attributes' => array('title' => 'Test menu link 2')),
-      'module' => 'menu',
-      'hidden' => 0,
-      'external' => 0,
-      'has_children' => 0,
-      'expanded' => 0,
-      'weight' => 12,
-      'depth' => 2,
-      'customized' => 1,
-      'p1' => '138',
-      'p2' => '139',
-      'p3' => '0',
-      'p4' => '0',
-      'p5' => '0',
-      'p6' => '0',
-      'p7' => '0',
-      'p8' => '0',
-      'p9' => '0',
-      'updated' => '0',
-    ),
-  );
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setUp() {
-    // This array stores the database.
-    foreach ($this->expectedResults as $k => $row) {
-      $this->databaseContents['menu_links'][$k] = $row;
-      $this->databaseContents['menu_links'][$k]['options'] = serialize($this->databaseContents['menu_links'][$k]['options']);
-    }
-    parent::setUp();
-  }
-
-}
diff --git a/core/modules/migrate_drupal/config/schema/migrate_drupal.source.schema.yml b/core/modules/migrate_drupal/config/schema/migrate_drupal.source.schema.yml
index 86e801e..2b3c624 100644
--- a/core/modules/migrate_drupal/config/schema/migrate_drupal.source.schema.yml
+++ b/core/modules/migrate_drupal/config/schema/migrate_drupal.source.schema.yml
@@ -50,6 +50,154 @@ migrate.source.variable_multirow:
         type: string
         label: 'Variable'
 
+migrate.source.d6_comment_variable:
+  type: migrate_source_sql
+  label: 'Drupal 6 comment variable'
+  mapping:
+    constants:
+      type: migrate_entity_constant
+      label: 'Constants'
+
+migrate.source.d6_contact_settings:
+  type: migrate_source_sql
+  label: 'Drupal 6 contact settings'
+  mapping:
+    variables:
+      type: sequence
+      label: 'Variables'
+      sequence:
+        type: string
+        label: 'Variable'
+
+migrate.source.d6_view_mode:
+  type: migrate_source_sql
+  label: 'Drupal 6 view mode'
+  mapping:
+    constants:
+      type: mapping
+      label: 'Constants'
+      mapping:
+        targetEntityType:
+          type: string
+          label: 'Target entity type'
+        status:
+          type: boolean
+          label: 'Status'
+
+migrate.source.d6_profile_field:
+  type: migrate_source_sql
+  label: 'Drupal 6 profile field'
+  mapping:
+    constants:
+      type: migrate_entity_constant
+      label: 'Constants'
+
+migrate.source.d6_field_formatter_settings:
+  type: migrate_source_sql
+  label: 'Drupal 6 field formatter settings'
+  mapping:
+    constants:
+      type: migrate_entity_constant
+      label: 'Constants'
+
+migrate.source.d6_node_type:
+  type: migrate_source_sql
+  label: 'Drupal 6 node type'
+  mapping:
+    constants:
+      type: migrate_entity_constant
+      label: 'Constants'
+
+migrate.source.d6_node:
+  type: migrate_source_sql
+  label: 'Drupal 6 node'
+  mapping:
+    node_type:
+      # The node type can be specified both as a string ID of a node type or an
+      # array of node type IDs, so there is no way to consistently parse this.
+      type: ignore
+      label: 'Node type'
+
+migrate.source.d6_node_revision:
+  type: migrate_source_sql
+  label: 'Drupal 6 node'
+  mapping:
+    node_type:
+      # The node type can be specified both as a string ID of a node type or an
+      # array of node type IDs, so there is no way to consistently parse this.
+      type: ignore
+      label: 'Node type'
+
+migrate.source.d6_upload_instance:
+  type: migrate_source_sql
+  label: 'Drupal 6 upload form display'
+  mapping:
+    constants:
+      type: migrate_entity_constant
+      label: 'Constants'
+
+migrate.source.d6_field_instance:
+  type: migrate_source_sql
+  label: 'Drupal 6 field instance'
+  mapping:
+    constants:
+      type: migrate_entity_constant
+      label: 'Constants'
+
+migrate.source.d6_field_instance_per_view_mode:
+  type: migrate_source_sql
+  label: 'Drupal 6 field formatter'
+  mapping:
+    constants:
+      type: migrate_entity_constant
+      label: 'Constants'
+
+migrate.source.d6_user:
+  type: migrate_source_sql
+  label: 'Drupal 6 user'
+  mapping:
+    constants:
+      type: mapping
+      label: 'Constants'
+      mapping:
+        key:
+          type: string
+          label: 'User data key'
+        module:
+          type: string
+          label: 'Module name'
+
+migrate.source.d6_user_picture_file:
+  type: migrate_source_sql
+  label: 'Drupal 6 user picure display'
+  mapping:
+    constants:
+      type: mapping
+      label: 'Constant'
+      mapping:
+        is_public:
+          type: boolean
+          label: 'Public'
+
+migrate.source.d6_user_picture_instance:
+  type: migrate_source_sql
+  label: 'Drupal 6 user picure display'
+  mapping:
+    provider:
+      type: string
+      label: 'Provider'
+    constants:
+      type: migrate_entity_constant
+      label: 'Constants'
+
+migrate.source.menu_link:
+  type: migrate_source_sql
+  label: 'Menu links'
+  mapping:
+    constants:
+      type: migrate_entity_constant
+      label: 'Constants'
+
 migrate_entity_constant:
   type: mapping
   mapping:
diff --git a/core/modules/system/migration_templates/d6_menu.yml b/core/modules/system/migration_templates/d6_menu.yml
index 0d353c2..a4c33db 100644
--- a/core/modules/system/migration_templates/d6_menu.yml
+++ b/core/modules/system/migration_templates/d6_menu.yml
@@ -3,7 +3,7 @@ label: Drupal 6 menus
 migration_tags:
   - Drupal 6
 source:
-  plugin: d6_menu
+  plugin: menu
 process:
   id: menu_name
   label: title
diff --git a/core/modules/system/migration_templates/d7_menu.yml b/core/modules/system/migration_templates/d7_menu.yml
new file mode 100644
index 0000000..a0bf626
--- /dev/null
+++ b/core/modules/system/migration_templates/d7_menu.yml
@@ -0,0 +1,12 @@
+id: d7_menu
+label: Drupal 7 menus
+migration_tags:
+  - Drupal 7
+source:
+  plugin: menu
+process:
+  id: menu_name
+  label: title
+  description: description
+destination:
+  plugin: entity:menu
diff --git a/core/modules/system/src/Plugin/migrate/source/Menu.php b/core/modules/system/src/Plugin/migrate/source/Menu.php
new file mode 100644
index 0000000..da5532a
--- /dev/null
+++ b/core/modules/system/src/Plugin/migrate/source/Menu.php
@@ -0,0 +1,50 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\system\Plugin\migrate\source\Menu.
+ */
+
+namespace Drupal\system\Plugin\migrate\source;
+
+use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
+
+/**
+ * Menu source from database.
+ *
+ * @MigrateSource(
+ *   id = "menu",
+ *   source_provider = "menu"
+ * )
+ */
+class Menu extends DrupalSqlBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    $query = $this->select('menu_custom', 'm')
+      ->fields('m', array('menu_name', 'title', 'description'));
+    return $query;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    return array(
+      'menu_name' => $this->t('The menu name. Primary key.'),
+      'title' => $this->t('The human-readable name of the menu.'),
+      'description' => $this->t('A description of the menu'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    $ids['menu_name']['type'] = 'string';
+    return $ids;
+  }
+
+}
diff --git a/core/modules/system/src/Plugin/migrate/source/d6/Menu.php b/core/modules/system/src/Plugin/migrate/source/d6/Menu.php
deleted file mode 100644
index 65aa876..0000000
--- a/core/modules/system/src/Plugin/migrate/source/d6/Menu.php
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\system\Plugin\migrate\source\d6\Menu.
- */
-
-namespace Drupal\system\Plugin\migrate\source\d6;
-
-use Drupal\migrate_drupal\Plugin\migrate\source\DrupalSqlBase;
-
-/**
- * Drupal 6 menu source from database.
- *
- * @MigrateSource(
- *   id = "d6_menu",
- *   source_provider = "menu"
- * )
- */
-class Menu extends DrupalSqlBase {
-
-  /**
-   * {@inheritdoc}
-   */
-  public function query() {
-    $query = $this->select('menu_custom', 'm')
-      ->fields('m', array('menu_name', 'title', 'description'));
-    return $query;
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function fields() {
-    return array(
-      'menu_name' => $this->t('The menu name. Primary key.'),
-      'title' => $this->t('The human-readable name of the menu.'),
-      'description' => $this->t('A description of the menu'),
-    );
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function getIds() {
-    $ids['menu_name']['type'] = 'string';
-    return $ids;
-  }
-
-}
diff --git a/core/modules/system/src/Tests/Migrate/d7/MigrateMenuTest.php b/core/modules/system/src/Tests/Migrate/d7/MigrateMenuTest.php
new file mode 100644
index 0000000..d07250c
--- /dev/null
+++ b/core/modules/system/src/Tests/Migrate/d7/MigrateMenuTest.php
@@ -0,0 +1,67 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\menu_link_content\Tests\d7\Migrate\MigrateMenuTest
+ */
+
+namespace Drupal\system\Tests\Migrate\d7;
+
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate_drupal\Tests\d7\MigrateDrupal7TestBase;
+use Drupal\Core\Database\Database;
+use Drupal\system\Entity\Menu;
+use Drupal\system\MenuInterface;
+
+/**
+ * Upgrade menus to system.menu.*.yml.
+ *
+ * @group system
+ * @dumpFile d7/MenuCustom.php
+ * @migration d7_menu
+ */
+class MigrateMenuTest extends MigrateDrupal7TestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+    $this->loadDumps(['MenuCustom.php']);
+    $this->executeMigration('d7_menu');
+  }
+
+  protected function assertEntity($id, $label, $description) {
+    /** @var \Drupal\system\MenuInterface $entity */
+    $entity = Menu::load($id);
+    $this->assertTrue($entity instanceof MenuInterface);
+    $this->assertIdentical($label, $entity->label());
+    $this->assertIdentical($description, $entity->getDescription());
+  }
+
+  /**
+   * Tests the Drupal 7 menu to Drupal 8 migration.
+   */
+  public function testMenu() {
+    $this->assertEntity('main-menu', 'Main menu', 'The <em>Main</em> menu is used on many sites to show the major sections of the site, often in a top navigation bar.');
+    $this->assertEntity('management', 'Management', 'The <em>Management</em> menu contains links for administrative tasks.');
+    $this->assertEntity('menu-test-menu', 'Test Menu', 'Test menu description.');
+    $this->assertEntity('navigation', 'Navigation', 'The <em>Navigation</em> menu contains links intended for site visitors. Links are added to the <em>Navigation</em> menu automatically by some modules.');
+    $this->assertEntity('user-menu', 'User menu', "The <em>User</em> menu contains links related to the user's account, as well as the 'Log out' link.");
+
+    // Test that we can re-import using the ConfigEntityBase destination.
+    Database::getConnection('default', 'migrate')
+      ->update('menu_custom')
+      ->fields(array('title' => 'Home Navigation'))
+      ->condition('menu_name', 'navigation')
+      ->execute();
+
+    db_truncate(entity_load('migration', 'd7_menu')->getIdMap()->mapTableName())->execute();
+    $migration = entity_load_unchanged('migration', 'd7_menu');
+    $executable = new MigrateExecutable($migration, $this);
+    $executable->import();
+
+    $this->assertEntity('navigation', 'Home Navigation', 'The <em>Navigation</em> menu contains links intended for site visitors. Links are added to the <em>Navigation</em> menu automatically by some modules.');
+  }
+
+}
diff --git a/core/modules/system/tests/src/Unit/Migrate/source/d6/MenuTest.php b/core/modules/system/tests/src/Unit/Migrate/source/d6/MenuTest.php
deleted file mode 100644
index f4eb567..0000000
--- a/core/modules/system/tests/src/Unit/Migrate/source/d6/MenuTest.php
+++ /dev/null
@@ -1,62 +0,0 @@
-<?php
-
-/**
- * @file
- * Contains \Drupal\Tests\system\Unit\Migrate\source\d6\MenuTest.
- */
-
-namespace Drupal\Tests\system\Unit\Migrate\source\d6;
-
-use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
-
-/**
- * Tests D6 menu source plugin.
- *
- * @group system
- */
-class MenuTest extends MigrateSqlSourceTestCase {
-
-  // The plugin system is not working during unit testing so the source plugin
-  // class needs to be manually specified.
-  const PLUGIN_CLASS = 'Drupal\system\Plugin\migrate\source\d6\Menu';
-
-  // The fake Migration configuration entity.
-  protected $migrationConfiguration = array(
-    // The ID of the entity, can be any string.
-    'id' => 'test',
-    // Leave it empty for now.
-    'idlist' => array(),
-    // This needs to be the identifier of the actual key: cid for comment, nid
-    // for node and so on.
-    'source' => array(
-      'plugin' => 'd6_menu',
-    ),
-  );
-
-  // We need to set up the database contents; it's easier to do that below.
-
-  protected $expectedResults = array(
-    array(
-      'menu_name' => 'menu-name-1',
-      'title' => 'menu custom value 1',
-      'description' => 'menu custom description value 1',
-    ),
-    array(
-      'menu_name' => 'menu-name-2',
-      'title' => 'menu custom value 2',
-      'description' => 'menu custom description value 2',
-    ),
-  );
-
-  /**
-   * {@inheritdoc}
-   */
-  protected function setUp() {
-    // This array stores the database.
-    foreach ($this->expectedResults as $k => $row) {
-      $this->databaseContents['menu_custom'][$k] = $row;
-    }
-    parent::setUp();
-  }
-
-}
diff --git a/core/modules/system/tests/src/Unit/Plugin/migrate/source/MenuTest.php b/core/modules/system/tests/src/Unit/Plugin/migrate/source/MenuTest.php
new file mode 100644
index 0000000..8673655
--- /dev/null
+++ b/core/modules/system/tests/src/Unit/Plugin/migrate/source/MenuTest.php
@@ -0,0 +1,77 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\system\Unit\Plugin\migrate\source\MenuTest.
+ */
+
+namespace Drupal\Tests\system\Unit\Plugin\migrate\source;
+
+use Drupal\Tests\migrate\Unit\MigrateSqlSourceTestCase;
+
+/**
+ * Tests menu source plugin.
+ *
+ * @group system
+ */
+class MenuTest extends MigrateSqlSourceTestCase {
+
+  // The plugin system is not working during unit testing so the source plugin
+  // class needs to be manually specified.
+  const PLUGIN_CLASS = 'Drupal\system\Plugin\migrate\source\Menu';
+
+  // The fake Migration configuration entity.
+  protected $migrationConfiguration = array(
+    // The ID of the entity, can be any string.
+    'id' => 'test',
+    // Leave it empty for now.
+    'idlist' => array(),
+    // This needs to be the identifier of the actual key: cid for comment, nid
+    // for node and so on.
+    'source' => array(
+      'plugin' => 'menu',
+    ),
+  );
+
+  // We need to set up the database contents; it's easier to do that below.
+
+  protected $expectedResults = array(
+    array(
+      'menu_name' => 'menu-name-1',
+      'title' => 'menu custom value 1',
+      'description' => 'menu custom description value 1',
+    ),
+    array(
+      'menu_name' => 'menu-name-2',
+      'title' => 'menu custom value 2',
+      'description' => 'menu custom description value 2',
+    ),
+  );
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    // This array stores the database.
+    foreach ($this->expectedResults as $k => $row) {
+      $this->databaseContents['menu_custom'][$k] = $row;
+    }
+    parent::setUp();
+  }
+
+}
+
+namespace Drupal\Tests\system\Unit\Plugin\migrate\source;
+
+use Drupal\Core\Database\Connection;
+use Drupal\Core\Extension\ModuleHandlerInterface;
+use Drupal\system\Plugin\migrate\source\Menu;
+
+class TestMenu extends Menu {
+  public function setDatabase(Connection $database) {
+    $this->database = $database;
+  }
+  public function setModuleHandler(ModuleHandlerInterface $module_handler) {
+    $this->moduleHandler = $module_handler;
+  }
+}
