diff --git a/core/modules/migrate_drupal/tests/src/Traits/CreateTestContentEntitiesTrait.php b/core/modules/migrate_drupal/tests/src/Traits/CreateTestContentEntitiesTrait.php
index db720e8cb7..2f0edc4259 100644
--- a/core/modules/migrate_drupal/tests/src/Traits/CreateTestContentEntitiesTrait.php
+++ b/core/modules/migrate_drupal/tests/src/Traits/CreateTestContentEntitiesTrait.php
@@ -57,7 +57,7 @@ protected function installEntitySchemas() {
   }
 
   /**
-   * Create several pieces of generic content.
+   * Adds aggregator content to the created content.
    */
   protected function createContent() {
     // Create an aggregator feed.
@@ -74,7 +74,13 @@ protected function createContent() {
       'link' => 'http://www.example.com',
     ]);
     $item->save();
+    $this->createContentUpgradeTest();
+  }
 
+  /**
+   * Create several pieces of generic content.
+   */
+  protected function createContentUpgradeTest() {
     // Create a block content.
     $block = BlockContent::create([
       'info' => 'block',
diff --git a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php
index 898a8a5568..0f80b478ca 100644
--- a/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php
+++ b/core/modules/migrate_drupal_ui/src/Form/MigrateUpgradeForm.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\migrate_drupal_ui\Form;
 
+use Drupal\Core\Database\DatabaseExceptionWrapper;
 use Drupal\Core\Datetime\DateFormatterInterface;
 use Drupal\Core\Extension\ModuleHandlerInterface;
 use Drupal\Core\Form\ConfirmFormBase;
@@ -166,9 +167,16 @@ public function buildOverviewForm(array $form, FormStateInterface $form_state) {
       //   https://www.drupal.org/node/2687849
       $form['upgrade_option_item'] = [
         '#type' => 'item',
-        '#prefix' => $this->t('An upgrade has already been performed on this site. To perform a new migration, create a clean and empty new install of Drupal 8. Rollbacks and incremental migrations are not yet supported through the user interface. For more information, see the <a href=":url">upgrading handbook</a>.', [':url' => 'https://www.drupal.org/upgrade/migrate']),
+        '#prefix' => $this->t('An upgrade has already been performed on this site. To perform a new migration, create a clean and empty new install of Drupal 8. Rollbacks are not yet supported through the user interface. For more information, see the <a href=":url">upgrading handbook</a>.', [':url' => 'https://www.drupal.org/upgrade/migrate']),
         '#description' => $this->t('Last upgrade: @date', ['@date' => $this->dateFormatter->format($date_performed)]),
       ];
+      $form['actions']['incremental'] = [
+        '#type' => 'submit',
+        '#value' => $this->t('Import new configuration and content from old site'),
+        '#button_type' => 'primary',
+        '#validate' => ['::validateIncrementalForm'],
+        '#submit' => ['::submitIncrementalForm'],
+      ];
       return $form;
     }
     else {
@@ -236,6 +244,51 @@ public function submitOverviewForm(array &$form, FormStateInterface $form_state)
   }
 
   /**
+   * Validation handler for the incremental overview form.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  public function validateIncrementalForm(array &$form, FormStateInterface $form_state) {
+    // Retrieve the database driver from state.
+    $database_state_key = $this->state->get('migrate.fallback_state_key', '');
+    if ($database_state_key) {
+      try {
+        $database = $this->state->get($database_state_key, [])['database'];
+        if ($connection = $this->getConnection($database)) {
+          if ($version = $this->getLegacyDrupalVersion($connection)) {
+            $this->setupMigrations($database, $form_state);
+            $valid_legacy_database = TRUE;
+          }
+        }
+      }
+      catch (DatabaseExceptionWrapper $exception) {
+        // Hide DB exceptions and forward to the DB credentials form. In that
+        // form we can more properly display errors and accept new credentials.
+      }
+    }
+    if (empty($valid_legacy_database)) {
+      $form_state->setValue('step', 'credentials');
+      $form_state->setRebuild();
+    }
+  }
+
+  /**
+   * Form submission handler for the incremental overview form.
+   *
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  public function submitIncrementalForm(array &$form, FormStateInterface $form_state) {
+    $form_state->set('step', 'confirm_id_conflicts');
+    $form_state->setRebuild();
+  }
+
+  /**
    * Builds the database credential form and adds file location information.
    *
    * This is largely borrowed from \Drupal\Core\Installer\Form\SiteSettingsForm.
@@ -415,31 +468,7 @@ public function validateCredentialForm(array &$form, FormStateInterface $form_st
         ]));
       }
       else {
-        $this->createDatabaseStateSettings($database, $version);
-        $migrations = $this->getMigrations('migrate_drupal_' . $version, $version);
-
-        // Get the system data from source database.
-        $system_data = $this->getSystemData($connection);
-
-        // Convert the migration object into array
-        // so that it can be stored in form storage.
-        $migration_array = [];
-        foreach ($migrations as $migration) {
-          $migration_array[$migration->id()] = $migration->label();
-        }
-
-        // Store the retrieved migration IDs in form storage.
-        $form_state->set('version', $version);
-        $form_state->set('migrations', $migration_array);
-        if ($version == 6) {
-          $form_state->set('source_base_path', $form_state->getValue('d6_source_base_path'));
-        }
-        else {
-          $form_state->set('source_base_path', $form_state->getValue('source_base_path'));
-        }
-        $form_state->set('source_private_file_path', $form_state->getValue('source_private_file_path'));
-        // Store the retrived system data in form storage.
-        $form_state->set('system_data', $system_data);
+        $this->setupMigrations($database, $form_state);
       }
     }
     catch (\Exception $e) {
@@ -848,6 +877,44 @@ protected function getDatabaseTypes() {
   }
 
   /**
+   * Setup migrations.
+   *
+   * @param array $database
+   *   Database array representing the source Drupal database.
+   * @param \Drupal\Core\Form\FormStateInterface $form_state
+   *   The current state of the form.
+   */
+  protected function setupMigrations(array $database, FormStateInterface $form_state) {
+    $connection = $this->getConnection($database);
+    $version = $this->getLegacyDrupalVersion($connection);
+    $this->createDatabaseStateSettings($database, $version);
+    $migrations = $this->getMigrations('migrate_drupal_' . $version, $version);
+
+    // Get the system data from source database.
+    $system_data = $this->getSystemData($connection);
+
+    // Convert the migration object into array
+    // so that it can be stored in form storage.
+    $migration_array = [];
+    foreach ($migrations as $migration) {
+      $migration_array[$migration->id()] = $migration->label();
+    }
+
+    // Store the retrieved migration IDs in form storage.
+    $form_state->set('version', $version);
+    $form_state->set('migrations', $migration_array);
+    if ($version == 6) {
+      $form_state->set('source_base_path', $form_state->getValue('d6_source_base_path'));
+    }
+    else {
+      $form_state->set('source_base_path', $form_state->getValue('source_base_path'));
+    }
+    $form_state->set('source_private_file_path', $form_state->getValue('source_private_file_path'));
+    // Store the retrived system data in form storage.
+    $form_state->set('system_data', $system_data);
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function getQuestion() {
diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php
index 98c05aaef4..89d908e5ce 100644
--- a/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php
+++ b/core/modules/migrate_drupal_ui/tests/src/Functional/MigrateUpgradeTestBase.php
@@ -40,7 +40,6 @@
     'content_translation',
     'migrate_drupal_ui',
     'telephone',
-    'aggregator',
     'book',
     'forum',
     'statistics',
@@ -59,7 +58,7 @@ protected function setUp() {
     $this->drupalLogin($this->rootUser);
 
     // Create content.
-    $this->createContent();
+    $this->createContentUpgradeTest();
   }
 
   /**
@@ -118,6 +117,12 @@ protected function tearDown() {
 
   /**
    * Executes all steps of migrations upgrade.
+   *
+   * The upgrade is started three times. The first time is to test that
+   * providing incorrect database credentials fails as expected. The second
+   * time is to run the migration and assert the results. The third time is
+   * to test an incremental migration, by installing the aggregator module,
+   * and assert the results.
    */
   public function testMigrateUpgrade() {
     $connection_options = $this->sourceDatabase->getConnectionOptions();
@@ -160,19 +165,7 @@ public function testMigrateUpgrade() {
     $this->assertText('Resolve the issue below to continue the upgrade.');
 
     $this->drupalPostForm(NULL, $edits, t('Review upgrade'));
-    $session->pageTextContains('WARNING: Content may be overwritten on your new site.');
-    $session->pageTextContains('There is conflicting content of these types:');
-    $session->pageTextContains('aggregator feed entities');
-    $session->pageTextContains('aggregator feed item entities');
-    $session->pageTextContains('custom block entities');
-    $session->pageTextContains('custom menu link entities');
-    $session->pageTextContains('file entities');
-    $session->pageTextContains('taxonomy term entities');
-    $session->pageTextContains('user entities');
-    $session->pageTextContains('comments');
-    $session->pageTextContains('content item revisions');
-    $session->pageTextContains('content items');
-    $session->pageTextContains('There is translated content of these types:');
+    $this->assertIdConflict($session);
     $this->drupalPostForm(NULL, [], t('I acknowledge I may lose data. Continue anyway.'));
     $this->assertResponse(200);
     $this->assertText('Upgrade analysis report');
@@ -187,78 +180,48 @@ public function testMigrateUpgrade() {
     // Restart the upgrade process.
     $this->drupalGet('/upgrade');
     $session->responseContains('Upgrade a site by importing its files and the data from its database into a clean and empty new install of Drupal 8.');
-
     $this->drupalPostForm(NULL, [], t('Continue'));
     $session->pageTextContains('Provide credentials for the database of the Drupal site you want to upgrade.');
     $session->fieldExists('mysql[host]');
-
     $this->drupalPostForm(NULL, $edits, t('Review upgrade'));
-    $session->pageTextContains('WARNING: Content may be overwritten on your new site.');
+    $this->assertIdConflict($session);
+
     $this->drupalPostForm(NULL, [], t('I acknowledge I may lose data. Continue anyway.'));
     $session->statusCodeEquals(200);
-    $session->pageTextContains('Upgrade analysis report');
-    // Ensure there are no errors about the missing modules from the test module.
-    $session->pageTextNotContains(t('Source module not found for migration_provider_no_annotation.'));
-    $session->pageTextNotContains(t('Source module not found for migration_provider_test.'));
-    $session->pageTextNotContains(t('Destination module not found for migration_provider_test'));
-    // Ensure there are no errors about any other missing migration providers.
-    $session->pageTextNotContains(t('module not found'));
-
-    // Test the available migration paths.
     $all_available = $this->getAvailablePaths();
-    foreach ($all_available as $available) {
-      $session->elementExists('xpath', "//span[contains(@class, 'checked') and text() = '$available']");
-      $session->elementNotExists('xpath', "//span[contains(@class, 'warning') and text() = '$available']");
-    }
-
-    // Test the missing migration paths.
     $all_missing = $this->getMissingPaths();
-    foreach ($all_missing as $missing) {
-      $session->elementExists('xpath', "//span[contains(@class, 'warning') and text() = '$missing']");
-      $session->elementNotExists('xpath', "//span[contains(@class, 'checked') and text() = '$missing']");
-    }
-
+    $this->assertReviewPage($session, $all_available, $all_missing);
     $this->drupalPostForm(NULL, [], t('Perform upgrade'));
     $this->assertText(t('Congratulations, you upgraded Drupal!'));
 
-    // Have to reset all the statics after migration to ensure entities are
-    // loadable.
-    $this->resetAll();
-
-    $expected_counts = $this->getEntityCounts();
-    foreach (array_keys(\Drupal::entityTypeManager()
-      ->getDefinitions()) as $entity_type) {
-      $real_count = \Drupal::entityQuery($entity_type)->count()->execute();
-      $expected_count = isset($expected_counts[$entity_type]) ? $expected_counts[$entity_type] : 0;
-      $this->assertEqual($expected_count, $real_count, "Found $real_count $entity_type entities, expected $expected_count.");
-    }
-
-    $plugin_manager = \Drupal::service('plugin.manager.migration');
-    /** @var \Drupal\migrate\Plugin\Migration[] $all_migrations */
-    $all_migrations = $plugin_manager->createInstancesByTag('Drupal ' . $version);
-    foreach ($all_migrations as $migration) {
-      $id_map = $migration->getIdMap();
-      foreach ($id_map as $source_id => $map) {
-        // Convert $source_id into a keyless array so that
-        // \Drupal\migrate\Plugin\migrate\id_map\Sql::getSourceHash() works as
-        // expected.
-        $source_id_values = array_values(unserialize($source_id));
-        $row = $id_map->getRowBySource($source_id_values);
-        $destination = serialize($id_map->currentDestination());
-        $message = "Migration of $source_id to $destination as part of the {$migration->id()} migration. The source row status is " . $row['source_row_status'];
-        // A completed migration should have maps with
-        // MigrateIdMapInterface::STATUS_IGNORED or
-        // MigrateIdMapInterface::STATUS_IMPORTED.
-        if ($row['source_row_status'] == MigrateIdMapInterface::STATUS_FAILED || $row['source_row_status'] == MigrateIdMapInterface::STATUS_NEEDS_UPDATE) {
-          $this->fail($message);
-        }
-        else {
-          $this->pass($message);
-        }
-      }
-    }
     \Drupal::service('module_installer')->install(['forum']);
     \Drupal::service('module_installer')->install(['book']);
+
+    // Install aggregator module.
+    \Drupal::service('module_installer')->install(['aggregator']);
+
+    // Test incremental migration.
+    // Need to update available an dmissing path lists.
+    $this->drupalGet('/upgrade');
+    $session->responseContains('An upgrade has already been performed on this site. To perform a new migration, create a clean and empty new install of Drupal 8. Rollbacks are not yet supported through the user interface.');
+    $this->drupalPostForm(NULL, [], t('Import new configuration and content from old site'));
+    $session->pageTextContains('WARNING: Content may be overwritten on your new site.');
+    $session->pageTextContains('There is conflicting content of these types:');
+    $session->pageTextContains('file entities');
+    $session->pageTextContains('content item revisions');
+    $session->pageTextContains('There is translated content of these types:');
+    $session->pageTextContains('content items');
+
+    $this->drupalPostForm(NULL, [], t('I acknowledge I may lose data. Continue anyway.'));
+    $session->statusCodeEquals(200);
+    $all_available = $this->getAvailablePaths();
+    $all_available[] = 'aggregator';
+    $all_missing = $this->getMissingPaths();
+    $all_missing = array_diff($all_missing, ['aggregator']);
+    $this->assertReviewPage($session, $all_available, $all_missing);
+    $this->drupalPostForm(NULL, [], t('Perform upgrade'));
+    $session->pageTextContains(t('Congratulations, you upgraded Drupal!'));
+    $this->assertMigrationResults($this->getEntityCountsIncremental(), $version);
   }
 
   /**
@@ -315,4 +278,111 @@ protected function translatePostValues(array $values) {
    */
   abstract protected function getMissingPaths();
 
+  /**
+   * Gets the expected number of entities per entity type after incremental.
+   *
+   * @return int[]
+   *   An array of expected counts keyed by entity type ID.
+   */
+  abstract protected function getEntityCountsIncremental();
+
+
+  /**
+   * Helper method to assert the text on the 'Upgrade analysis report' page.
+   *
+   * @param $session
+   *   The currenct session.
+   * @param $all_available
+   *   Array of modules that will be upgraded.
+   * @param $all_missing
+   *   Array of modules that will not be upgraded.
+   */
+  protected function assertReviewPage($session, $all_available, $all_missing) {
+    $this->assertText('Upgrade analysis report');
+
+    // Ensure there are no errors about the missing modules from the test module.
+    $session->pageTextNotContains(t('Source module not found for migration_provider_no_annotation.'));
+    $session->pageTextNotContains(t('Source module not found for migration_provider_test.'));
+    $session->pageTextNotContains(t('Destination module not found for migration_provider_test'));
+    // Ensure there are no errors about any other missing migration providers.
+    $session->pageTextNotContains(t('module not found'));
+
+    // Test the available migration paths.
+    foreach ($all_available as $available) {
+      $session->elementExists('xpath', "//span[contains(@class, 'checked') and text() = '$available']");
+      $session->elementNotExists('xpath', "//span[contains(@class, 'warning') and text() = '$available']");
+    }
+
+    // Test the missing migration paths.
+    foreach ($all_missing as $missing) {
+      $session->elementExists('xpath', "//span[contains(@class, 'warning') and text() = '$missing']");
+      $session->elementNotExists('xpath', "//span[contains(@class, 'checked') and text() = '$missing']");
+    }
+  }
+
+  /**
+   * Helper method that asserts text on the ID conflict form.
+   *
+   * @param $session
+   *   The currenct session.
+   */
+  protected function assertIdConflict($session) {
+    $session->pageTextContains('WARNING: Content may be overwritten on your new site.');
+    $session->pageTextContains('There is conflicting content of these types:');
+    $session->pageTextContains('custom block entities');
+    $session->pageTextContains('custom menu link entities');
+    $session->pageTextContains('file entities');
+    $session->pageTextContains('taxonomy term entities');
+    $session->pageTextContains('user entities');
+    $session->pageTextContains('comments');
+    $session->pageTextContains('content item revisions');
+    $session->pageTextContains('content items');
+    $session->pageTextContains('There is translated content of these types:');
+  }
+
+  /**
+   * Checks that migrations have been performed successfully.
+   *
+   * @param array $expected_counts
+   *   The expected counts of each entity type.
+   * @param string $version
+   *   The drupal verison.
+   *
+   */
+  protected function assertMigrationResults(array $expected_counts, $version) {
+    // Have to reset all the statics after migration to ensure entities are
+    // loadable.
+    $this->resetAll();
+    foreach (array_keys(\Drupal::entityTypeManager()->getDefinitions()) as $entity_type) {
+      $real_count = (int) \Drupal::entityQuery($entity_type)->count()->execute();
+      $expected_count = isset($expected_counts[$entity_type]) ? $expected_counts[$entity_type] : 0;
+      $this->assertSame($expected_count, $real_count, "Found $real_count $entity_type entities, expected $expected_count.");
+    }
+    $plugin_manager = \Drupal::service('plugin.manager.migration');
+    /** @var \Drupal\migrate\Plugin\Migration[] $all_migrations */
+    $all_migrations = $plugin_manager->createInstancesByTag('Drupal ' . $version);
+    foreach ($all_migrations as $migration) {
+      $id_map = $migration->getIdMap();
+      foreach ($id_map as $source_id => $map) {
+        // Convert $source_id into a keyless array so that
+        // \Drupal\migrate\Plugin\migrate\id_map\Sql::getSourceHash() works as
+        // expected.
+        $source_id_values = array_values(unserialize($source_id));
+        $row = $id_map->getRowBySource($source_id_values);
+        $destination = serialize($id_map->currentDestination());
+        $message = "Migration of $source_id to $destination as part of the {$migration->id()} migration. The source row status is " . $row['source_row_status'];
+        // A completed migration should have maps with
+        // MigrateIdMapInterface::STATUS_IGNORED or
+        // MigrateIdMapInterface::STATUS_IMPORTED.
+        if ($row['source_row_status'] == MigrateIdMapInterface::STATUS_FAILED || $row['source_row_status'] == MigrateIdMapInterface::STATUS_NEEDS_UPDATE) {
+          $this->fail($message);
+        }
+        else {
+          $this->pass($message);
+        }
+      }
+    }
+
+  }
+
 }
diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php
index 432b7036d7..ed16569328 100644
--- a/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php
+++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d6/MigrateUpgrade6Test.php
@@ -69,22 +69,39 @@ protected function getEntityCounts() {
       'user' => 7,
       'user_role' => 6,
       'menu_link_content' => 5,
-      'view' => 16,
+      'view' => 14,
       'date_format' => 11,
       'entity_form_display' => 29,
       'entity_form_mode' => 1,
-      'entity_view_display' => 53,
-      'entity_view_mode' => 14,
+      'entity_view_display' => 50,
+      'entity_view_mode' => 12,
       'base_field_override' => 38,
     ];
   }
+   /**
+    * {@inheritdoc}
+    */
+  protected function getEntityCountsIncremental() {
+    $counts = $this->getEntityCounts();
+    $counts['aggregator_feed'] = 1;
+    $counts['aggregator_item'] = 1;
+    $counts['base_field_override'] = 38;
+    $counts['entity_form_display'] = 29;
+    $counts['entity_view_display'] = 53;
+    $counts['entity_view_mode'] = 14;
+    $counts['field_config'] = 84;
+    $counts['field_storage_config'] = 58;
+    $counts['node_type'] = 13;
+    $counts['rdf_mapping'] = 7;
+    $counts['view'] = 16;
+    return $counts;
+  }
 
   /**
    * {@inheritdoc}
    */
   protected function getAvailablePaths() {
     return [
-      'aggregator',
       'block',
       'book',
       'comment',
@@ -129,6 +146,7 @@ protected function getAvailablePaths() {
    */
   protected function getMissingPaths() {
     return [
+      'aggregator',
       'date_api',
       'date_timezone',
       'event',
diff --git a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php
index 608967c754..84290db00c 100644
--- a/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php
+++ b/core/modules/migrate_drupal_ui/tests/src/Functional/d7/MigrateUpgrade7Test.php
@@ -39,8 +39,6 @@ protected function getSourceBasePath() {
    */
   protected function getEntityCounts() {
     return [
-      'aggregator_item' => 11,
-      'aggregator_feed' => 1,
       'block' => 25,
       'block_content' => 1,
       'block_content_type' => 1,
@@ -73,12 +71,12 @@ protected function getEntityCounts() {
       'user' => 4,
       'user_role' => 3,
       'menu_link_content' => 8,
-      'view' => 16,
+      'view' => 14,
       'date_format' => 11,
       'entity_form_display' => 17,
       'entity_form_mode' => 1,
-      'entity_view_display' => 28,
-      'entity_view_mode' => 14,
+      'entity_view_display' => 25,
+      'entity_view_mode' => 12,
       'base_field_override' => 9,
     ];
   }
@@ -86,9 +84,27 @@ protected function getEntityCounts() {
   /**
    * {@inheritdoc}
    */
+  protected function getEntityCountsIncremental() {
+    $counts = $this->getEntityCounts();
+    $counts['aggregator_feed'] = 1;
+    $counts['aggregator_item'] = 10;
+    $counts['base_field_override'] = 9;
+    $counts['entity_form_display'] = 17;
+    $counts['entity_view_display'] = 28;
+    $counts['entity_view_mode'] = 14;
+    $counts['field_config'] = 61;
+    $counts['field_storage_config'] = 44;
+    $counts['rdf_mapping'] = 7;
+    $counts['taxonomy_term'] = 18;
+    $counts['view'] = 16;
+    return $counts;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
   protected function getAvailablePaths() {
     return [
-      'aggregator',
       'block',
       'comment',
       'contact',
@@ -132,6 +148,7 @@ protected function getAvailablePaths() {
    */
   protected function getMissingPaths() {
     return [
+      'aggregator',
       'blog',
       'book',
       'color',
