diff --git a/config/install/migrate_plus.migration_group.default.yml b/config/install/migrate_plus.migration_group.default.yml
index 17105826b5a6a07457eba8b1a76c51ab2d83db11..c19fc56a20f01e959d790e2ebec936f47621b2e4 100644
--- a/config/install/migrate_plus.migration_group.default.yml
+++ b/config/install/migrate_plus.migration_group.default.yml
@@ -1,3 +1,4 @@
 # A default group, assigned to any migrations with no explicit group.
 id: default
 label: Default
+description: A container for any migrations not explicitly assigned to a group.
diff --git a/config/schema/migrate_plus.schema.yml b/config/schema/migrate_plus.schema.yml
index 08b47b26e4e741fcab43e20048d870ac76009e9a..af36c593ec0f6fadc5c37723ea63392a52833c9e 100644
--- a/config/schema/migrate_plus.schema.yml
+++ b/config/schema/migrate_plus.schema.yml
@@ -8,6 +8,12 @@ migrate_plus.migration_group.*:
     label:
       type: label
       label: 'Label'
+    description:
+      type: string
+      label: 'Description'
+    source_type:
+      type: string
+      label: 'Source type'
     module:
       type: string
       label: 'Dependent module'
diff --git a/migrate_example/config/install/migrate_plus.migration_group.beer.yml b/migrate_example/config/install/migrate_plus.migration_group.beer.yml
index bcc5f2b4497819d43d76c8ece8cb2fa2b7826c69..da0238f30a46d866685c379703d2dc7ca3b6515b 100644
--- a/migrate_example/config/install/migrate_plus.migration_group.beer.yml
+++ b/migrate_example/config/install/migrate_plus.migration_group.beer.yml
@@ -5,6 +5,12 @@ id: beer
 # A human-friendly label of the group, displayed in the UI.
 label: Beer Imports
 
+# More information about the group.
+description: A few simple beer-related imports, to demonstrate how to implement migrations.
+
+# Short description of the type of source, e.g. "Drupal 6" or "WordPress".
+source_type: Custom tables
+
 # Here we add any default configuration settings to be shared among all
 # migrations in the group.
 shared_configuration:
diff --git a/migrate_plus.module b/migrate_plus.module
index 9edd00c206641792075600908bc8a635689efdbc..aba4648710a7923398be233901ca1829c19a16cd 100644
--- a/migrate_plus.module
+++ b/migrate_plus.module
@@ -2,7 +2,7 @@
 
 /**
  * @file
- * Provides tools and enhancements for implementing and managing migrations.
+ * Provides enhancements for implementing and managing migrations.
  */
 
 use Drupal\migrate\Entity\MigrationInterface;
diff --git a/migrate_tools/migrate_tools.links.action.yml b/migrate_tools/migrate_tools.links.action.yml
new file mode 100644
index 0000000000000000000000000000000000000000..2a0a9adc56b5834a4e491b0ab7f2e035c5a96811
--- /dev/null
+++ b/migrate_tools/migrate_tools.links.action.yml
@@ -0,0 +1,13 @@
+migrate_tools.add_action:
+  route_name: entity.migration_group.add_form
+  title: 'Add migration group'
+  appears_on:
+    - entity.migration_group.list
+
+migrate_tools.list_action:
+  route_name: entity.migration_group.list
+  title: 'List Migration Groups'
+  appears_on:
+    - entity.migration_group.add_form
+    - entity.migration_group.edit
+    - entity.migration_group.delete
diff --git a/migrate_tools/migrate_tools.links.menu.yml b/migrate_tools/migrate_tools.links.menu.yml
new file mode 100644
index 0000000000000000000000000000000000000000..d55ba56c133d03491d28dfd33827bd42cb8928db
--- /dev/null
+++ b/migrate_tools/migrate_tools.links.menu.yml
@@ -0,0 +1,5 @@
+migrate_tools.menu:
+  title: Migrations
+  parent: system.admin_structure
+  description: Manage migration processes.
+  route_name: entity.migration_group.list
diff --git a/migrate_tools/migrate_tools.module b/migrate_tools/migrate_tools.module
new file mode 100644
index 0000000000000000000000000000000000000000..441343e0d6ccf0b13405f424d8d84e00436675bd
--- /dev/null
+++ b/migrate_tools/migrate_tools.module
@@ -0,0 +1,22 @@
+<?php
+
+/**
+ * @file
+ * Provides tools for implementing and managing migrations.
+ */
+
+/**
+ * Implements hook_entity_type_alter().
+ */
+function migrate_tools_entity_type_alter(array &$entity_types) {
+  // Inject our UI into the general migration config entity.
+  /** @var \Drupal\Core\Config\Entity\ConfigEntityType[] $entity_types */
+  $entity_types['migration_group']
+    ->set('admin_permission', 'administer migrations')
+    ->setHandlerClass('list_builder', 'Drupal\migrate_tools\Controller\MigrationGroupListBuilder')
+    ->setFormClass('add', 'Drupal\migrate_tools\Form\MigrationGroupAddForm')
+    ->setFormClass('edit', 'Drupal\migrate_tools\Form\MigrationGroupEditForm')
+    ->setFormClass('delete', 'Drupal\migrate_tools\Form\MigrationGroupDeleteForm')
+    ->setLinkTemplate('edit-form', '/admin/structure/migrate/manage/{migration_group}')
+    ->setLinkTemplate('delete-form', '/admin/structure/migrate/manage/{migration_group}/delete');
+}
diff --git a/migrate_tools/migrate_tools.permissions.yml b/migrate_tools/migrate_tools.permissions.yml
new file mode 100644
index 0000000000000000000000000000000000000000..b5301e5c1f326186a48ba7f97a8aa9cee66b8343
--- /dev/null
+++ b/migrate_tools/migrate_tools.permissions.yml
@@ -0,0 +1,3 @@
+'administer migrations':
+  title: 'Administer migrations'
+  description: Create, edit, and manage migration processed.
diff --git a/migrate_tools/migrate_tools.routing.yml b/migrate_tools/migrate_tools.routing.yml
new file mode 100644
index 0000000000000000000000000000000000000000..01a8242386f18afe928fc159a85740d15ed5db23
--- /dev/null
+++ b/migrate_tools/migrate_tools.routing.yml
@@ -0,0 +1,35 @@
+# This is the router item for listing all entities.
+entity.migration_group.list:
+  path: '/admin/structure/migrate'
+  defaults:
+    _entity_list: 'migration_group'
+    _title: 'Migrations'
+  requirements:
+    _permission: 'administer migrations'
+
+# This is the router item for adding our entity.
+entity.migration_group.add_form:
+  path: '/admin/structure/migrate/add'
+  defaults:
+    _title: 'Add migration group'
+    _entity_form: migration_group.add
+  requirements:
+    _entity_create_access: migration_group
+
+# This is the router item for editing our entity.
+entity.migration_group.edit_form:
+  path: '/admin/structure/migrate/manage/{migration_group}'
+  defaults:
+    _title: 'Edit migration group'
+    _entity_form: migration_group.edit
+  requirements:
+    _entity_access: migration_group.update
+
+# This is the router item for deleting an instance of our entity.
+entity.migration_group.delete_form:
+  path: '/admin/structure/migrate/manage/{migration_group}/delete'
+  defaults:
+    _title: 'Delete migration group'
+    _entity_form: migration_group.delete
+  requirements:
+    _entity_access: migration_group.delete
diff --git a/migrate_tools/src/Controller/MigrationGroupListBuilder.php b/migrate_tools/src/Controller/MigrationGroupListBuilder.php
new file mode 100644
index 0000000000000000000000000000000000000000..4a00c733439d7e8745f12746d079241151f4a71b
--- /dev/null
+++ b/migrate_tools/src/Controller/MigrationGroupListBuilder.php
@@ -0,0 +1,72 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\migrate_tools\Controller\MigrationGroupListBuilder.
+ */
+
+namespace Drupal\migrate_tools\Controller;
+
+use Drupal\Core\Config\Entity\ConfigEntityListBuilder;
+use Drupal\Core\Config\Entity\ConfigEntityInterface;
+use Drupal\Core\Entity\EntityInterface;
+
+/**
+ * Provides a listing of migration group entities.
+ *
+ * @package Drupal\migrate_tools\Controller
+ *
+ * @ingroup migrate_tools
+ */
+class MigrationGroupListBuilder extends ConfigEntityListBuilder {
+
+  /**
+   * Builds the header row for the entity listing.
+   *
+   * @return array
+   *   A render array structure of header strings.
+   *
+   * @see Drupal\Core\Entity\EntityListController::render()
+   */
+  public function buildHeader() {
+    $header['label'] = $this->t('Migration Group');
+    $header['machine_name'] = $this->t('Machine Name');
+    $header['description'] = $this->t('Description');
+    $header['source_type'] = $this->t('Source Type');
+    return $header + parent::buildHeader();
+  }
+
+  /**
+   * Builds a row for an entity in the entity listing.
+   *
+   * @param EntityInterface $entity
+   *   The entity for which to build the row.
+   *
+   * @return array
+   *   A render array of the table row for displaying the entity.
+   *
+   * @see Drupal\Core\Entity\EntityListController::render()
+   */
+  public function buildRow(ConfigEntityInterface $entity) {
+    $row['label'] = $this->getLabel($entity);
+    $row['machine_name'] = $entity->id();
+    $row['description'] = $entity->get('description');
+    $row['source_type'] = $entity->get('source_type');
+
+    return $row + parent::buildRow($entity);
+  }
+
+  /**
+   * Adds some descriptive text to our entity list.
+   *
+   * @return array
+   *   Renderable array.
+   */
+  public function render() {
+    $build['description'] = array(
+      '#markup' => $this->t("<p>Initial POC of the migration dashboard - note that management of the migrations themselves is not yet implemented, only the groups.</p>"),
+    );
+    $build[] = parent::render();
+    return $build;
+  }
+
+}
diff --git a/migrate_tools/src/Form/MigrationGroupAddForm.php b/migrate_tools/src/Form/MigrationGroupAddForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..f6da5046e66a36078d20abcae7461de4feedfbcf
--- /dev/null
+++ b/migrate_tools/src/Form/MigrationGroupAddForm.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\migrate_tools\Form\MigrationGroupAddForm.
+ */
+
+namespace Drupal\migrate_tools\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Class MigrationGroupAddForm.
+ *
+ * Provides the add form for our migration_group entity.
+ *
+ * @package Drupal\migrate_tools\Form
+ *
+ * @ingroup migrate_tools
+ */
+class MigrationGroupAddForm extends MigrationGroupFormBase {
+
+  /**
+   * Returns the actions provided by this form.
+   *
+   * For our add form, we only need to change the text of the submit button.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   An associative array containing the current state of the form.
+   *
+   * @return array
+   *   An array of supported actions for the current entity form.
+   */
+  protected function actions(array $form, FormStateInterface $form_state) {
+    $actions = parent::actions($form, $form_state);
+    $actions['submit']['#value'] = $this->t('Create Migration Group');
+    return $actions;
+  }
+
+}
diff --git a/migrate_tools/src/Form/MigrationGroupDeleteForm.php b/migrate_tools/src/Form/MigrationGroupDeleteForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..6b5194b200ab0b754c150ba5b7ac8e52f908a7f6
--- /dev/null
+++ b/migrate_tools/src/Form/MigrationGroupDeleteForm.php
@@ -0,0 +1,76 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\migrate_tools\Form\MigrationGroupDeleteForm.
+ */
+
+namespace Drupal\migrate_tools\Form;
+
+use Drupal\Core\Entity\EntityConfirmFormBase;
+use Drupal\Core\Url;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Class MigrationGroupDeleteForm.
+ *
+ * @package Drupal\migrate_tools\Form
+ *
+ * @ingroup migrate_tools
+ */
+class MigrationGroupDeleteForm extends EntityConfirmFormBase {
+
+  /**
+   * Gathers a confirmation question.
+   *
+   * @return string
+   *   Translated string.
+   */
+  public function getQuestion() {
+    return $this->t('Are you sure you want to delete migration group %label?', array(
+        '%label' => $this->entity->label(),
+    ));
+  }
+
+  /**
+   * Gather the confirmation text.
+   *
+   * @return string
+   *   Translated string.
+   */
+  public function getConfirmText() {
+    return $this->t('Delete Migration Group');
+  }
+
+  /**
+   * Gets the cancel URL.
+   *
+   * @return \Drupal\Core\Url
+   *   The URL to go to if the user cancels the deletion.
+   */
+  public function getCancelUrl() {
+    return new Url('entity.migration_group.list');
+  }
+
+  /**
+   * The submit handler for the confirm form.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   An associative array containing the current state of the form.
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Delete the entity.
+    $this->entity->delete();
+
+    // Set a message that the entity was deleted.
+    drupal_set_message(t('Migration group %label was deleted.', array(
+      '%label' => $this->entity->label(),
+    )));
+
+    // Redirect the user to the list controller when complete.
+    $form_state->setRedirectUrl($this->getCancelUrl());
+  }
+
+}
diff --git a/migrate_tools/src/Form/MigrationGroupEditForm.php b/migrate_tools/src/Form/MigrationGroupEditForm.php
new file mode 100644
index 0000000000000000000000000000000000000000..ffacb3d3ff44e43eb35111129999a5c348766da8
--- /dev/null
+++ b/migrate_tools/src/Form/MigrationGroupEditForm.php
@@ -0,0 +1,42 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\migrate_tools\Form\MigrationGroupEditForm.
+ */
+
+namespace Drupal\migrate_tools\Form;
+
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Class MigrationGroupEditForm
+ *
+ * Provides the edit form for our Migration Group entity.
+ *
+ * @package Drupal\migrate_tools\Form
+ *
+ * @ingroup migrate_tools
+ */
+class MigrationGroupEditForm extends MigrationGroupFormBase {
+
+  /**
+   * Returns the actions provided by this form.
+   *
+   * For the edit form, we only need to change the text of the submit button.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   An associative array containing the current state of the form.
+   *
+   * @return array
+   *   An array of supported actions for the current entity form.
+   */
+  public function actions(array $form, FormStateInterface $form_state) {
+    $actions = parent::actions($form, $form_state);
+    $actions['submit']['#value'] = t('Update Migration Group');
+    return $actions;
+  }
+
+}
diff --git a/migrate_tools/src/Form/MigrationGroupFormBase.php b/migrate_tools/src/Form/MigrationGroupFormBase.php
new file mode 100644
index 0000000000000000000000000000000000000000..e914cf3282246893c602da97cd4f141afe750b5d
--- /dev/null
+++ b/migrate_tools/src/Form/MigrationGroupFormBase.php
@@ -0,0 +1,189 @@
+<?php
+
+/**
+ * @file
+ * Contains Drupal\migrate_tools\Form\MigrationGroupFormBase.
+ */
+
+namespace Drupal\migrate_tools\Form;
+
+use Drupal\Core\Entity\EntityForm;
+use Drupal\Core\Entity\Query\QueryFactory;
+use Drupal\Core\Form\FormStateInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+use Drupal\migrate_plus\Entity\MigrationGroupInterface;
+
+/**
+ * Class MigrationGroupFormBase.
+ *
+ * @package Drupal\migrate_tools\Form
+ *
+ * @ingroup migrate_tools
+ */
+class MigrationGroupFormBase extends EntityForm {
+
+  /**
+   * @var \Drupal\Core\Entity\Query\QueryFactory
+   */
+  protected $entityQueryFactory;
+
+  /**
+   * Construct the MigrationGroupFormBase.
+   *
+   * For simple entity forms, there's no need for a constructor. Our migration group form
+   * base, however, requires an entity query factory to be injected into it
+   * from the container. We later use this query factory to build an entity
+   * query for the exists() method.
+   *
+   * @param \Drupal\Core\Entity\Query\QueryFactory $query_factory
+   *   An entity query factory for the migration group entity type.
+   */
+  public function __construct(QueryFactory $query_factory) {
+    $this->entityQueryFactory = $query_factory;
+  }
+
+  /**
+   * Factory method for MigrationGroupFormBase.
+   */
+  public static function create(ContainerInterface $container) {
+    return new static($container->get('entity.query'));
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::form().
+   *
+   * Builds the entity add/edit form.
+   *
+   * @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
+   *   An associative array containing the robot add/edit form.
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    // Get anything we need from the base class.
+    $form = parent::buildForm($form, $form_state);
+
+    /** @var MigrationGroupInterface $migration_group */
+    $migration_group = $this->entity;
+
+    // Build the form.
+    $form['label'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Label'),
+      '#maxlength' => 255,
+      '#default_value' => $migration_group->label(),
+      '#required' => TRUE,
+    );
+    $form['id'] = array(
+      '#type' => 'machine_name',
+      '#title' => $this->t('Machine name'),
+      '#default_value' => $migration_group->id(),
+      '#machine_name' => array(
+        'exists' => array($this, 'exists'),
+        'replace_pattern' => '([^a-z0-9_]+)|(^custom$)',
+        'error' => 'The machine-readable name must be unique, and can only contain lowercase letters, numbers, and underscores. Additionally, it can not be the reserved word "custom".',
+      ),
+      '#disabled' => !$migration_group->isNew(),
+    );
+    $form['description'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Description'),
+      '#maxlength' => 255,
+      '#default_value' => $migration_group->get('description'),
+    );
+    $form['source_type'] = array(
+      '#type' => 'textfield',
+      '#title' => $this->t('Source type'),
+      '#description' => $this->t('Type of source system the group is migrating from, for example "Drupal 6" or "WordPress 4".'),
+      '#maxlength' => 255,
+      '#default_value' => $migration_group->get('source_type'),
+    );
+
+    // Return the form.
+    return $form;
+  }
+
+  /**
+   * Checks for an existing migration group.
+   *
+   * @param string|int $entity_id
+   *   The entity ID.
+   * @param array $element
+   *   The form element.
+   * @param FormStateInterface $form_state
+   *   The form state.
+   *
+   * @return bool
+   *   TRUE if this format already exists, FALSE otherwise.
+   */
+  public function exists($entity_id, array $element, FormStateInterface $form_state) {
+    // Use the query factory to build a new robot entity query.
+    $query = $this->entityQueryFactory->get('migration_group');
+
+    // Query the entity ID to see if its in use.
+    $result = $query->condition('id', $element['#field_prefix'] . $entity_id)
+      ->execute();
+
+    // We don't need to return the ID, only if it exists or not.
+    return (bool) $result;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::actions().
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   An associative array containing the current state of the form.
+   *
+   * @return array
+   *   An array of supported actions for the current entity form.
+   */
+  protected function actions(array $form, FormStateInterface $form_state) {
+    // Get the basic actins from the base class.
+    $actions = parent::actions($form, $form_state);
+
+    // Change the submit button text.
+    $actions['submit']['#value'] = $this->t('Save');
+
+    // Return the result.
+    return $actions;
+  }
+
+  /**
+   * Overrides Drupal\Core\Entity\EntityFormController::save().
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   An associative array containing the current state of the form.
+   */
+  public function save(array $form, FormStateInterface $form_state) {
+    $migration_group = $this->getEntity();
+    $status = $migration_group->save();
+
+    // Grab the URL of the new entity. We'll use it in the message.
+    $url = $migration_group->urlInfo();
+
+    // Create an edit link.
+    $edit_link = $this->l(t('Edit'), $url);
+
+    if ($status == SAVED_UPDATED) {
+      // If we edited an existing entity...
+      drupal_set_message($this->t('Migration group %label has been updated.', array('%label' => $migration_group->label())));
+      $this->logger('contact')->notice('Migration group %label has been updated.', ['%label' => $migration_group->label(), 'link' => $edit_link]);
+    }
+    else {
+      // If we created a new entity...
+      drupal_set_message($this->t('Migration group %label has been added.', array('%label' => $migration_group->label())));
+      $this->logger('contact')->notice('Migration group %label has been added.', ['%label' => $migration_group->label(), 'link' => $edit_link]);
+    }
+
+    // Redirect the user back to the listing route after the save operation.
+    $form_state->setRedirect('entity.migration_group.list');
+  }
+
+}