diff --git a/core/modules/book/migration_templates/d6_book.yml b/core/modules/book/migration_templates/d6_book.yml
index 683f56a..0aaad33 100644
--- a/core/modules/book/migration_templates/d6_book.yml
+++ b/core/modules/book/migration_templates/d6_book.yml
@@ -20,4 +20,4 @@ destination:
   plugin: book
 migration_dependencies:
   required:
-    - d6_node
+    - d6_node:*
diff --git a/core/modules/migrate/config/schema/migrate.schema.yml b/core/modules/migrate/config/schema/migrate.schema.yml
index 5157525..88681c8 100644
--- a/core/modules/migrate/config/schema/migrate.schema.yml
+++ b/core/modules/migrate/config/schema/migrate.schema.yml
@@ -28,6 +28,9 @@ migrate.migration.*:
     destination:
       type: migrate.destination.[plugin]
       label: 'Destination'
+    template:
+      type: string
+      label: 'Template'
     migration_dependencies:
       type: mapping
       label: 'Dependencies'
diff --git a/core/modules/migrate/migrate.services.yml b/core/modules/migrate/migrate.services.yml
index c4e52e0..c223716 100644
--- a/core/modules/migrate/migrate.services.yml
+++ b/core/modules/migrate/migrate.services.yml
@@ -8,6 +8,9 @@ services:
   migrate.template_storage:
     class: Drupal\migrate\MigrateTemplateStorage
     arguments: ['@module_handler']
+  migrate.migration_builder:
+    class: Drupal\migrate\MigrationBuilder
+    arguments: ['@plugin.manager.migrate.builder']
   plugin.manager.migrate.source:
     class: Drupal\migrate\Plugin\MigratePluginManager
     arguments: [source, '@container.namespaces', '@cache.discovery', '@module_handler', 'Drupal\migrate\Annotation\MigrateSource']
@@ -20,6 +23,9 @@ services:
   plugin.manager.migrate.id_map:
     class: Drupal\migrate\Plugin\MigratePluginManager
     arguments: [id_map, '@container.namespaces', '@cache.discovery', '@module_handler']
+  plugin.manager.migrate.builder:
+    class: Drupal\migrate\Plugin\MigratePluginManager
+    arguments: [builder, '@container.namespaces', '@cache.discovery', '@module_handler']
   password_migrate:
     class: Drupal\migrate\MigratePassword
     arguments: ['@password_original']
diff --git a/core/modules/migrate/src/Entity/Migration.php b/core/modules/migrate/src/Entity/Migration.php
index c92f975..c287e1f 100644
--- a/core/modules/migrate/src/Entity/Migration.php
+++ b/core/modules/migrate/src/Entity/Migration.php
@@ -233,6 +233,13 @@ class Migration extends ConfigEntityBase implements MigrationInterface, Requirem
   protected $dependencies = [];
 
   /**
+   * The ID of the template from which this migration was derived, if any.
+   *
+   * @var string|NULL
+   */
+  protected $template;
+
+  /**
    * The entity manager.
    *
    * @var \Drupal\Core\Entity\EntityManagerInterface
diff --git a/core/modules/migrate/src/MigrationBuilder.php b/core/modules/migrate/src/MigrationBuilder.php
new file mode 100644
index 0000000..e123620
--- /dev/null
+++ b/core/modules/migrate/src/MigrationBuilder.php
@@ -0,0 +1,63 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\MigrationBuilder.
+ */
+
+namespace Drupal\migrate;
+
+use Drupal\migrate\Entity\Migration;
+use Drupal\migrate\Plugin\MigratePluginManager;
+
+class MigrationBuilder {
+
+  /**
+   * @var \Drupal\migrate\Plugin\MigratePluginManager
+   */
+  protected $builderManager;
+
+  /**
+   * Constructs a MigrationBuilder.
+   *
+   * @param \Drupal\migrate\Plugin\MigratePluginManager $builder_manager
+   *   The builder plugin manager.
+   */
+  public function __construct(MigratePluginManager $builder_manager) {
+    $this->builderManager = $builder_manager;
+  }
+
+  /**
+   * Builds migration entities from templates.
+   *
+   * @param array $templates
+   *   The parsed templates.
+   *
+   * @return \Drupal\migrate\Entity\MigrationInterface[]
+   *   The migration entities derived from the templates.
+   */
+  public function createMigrations(array $templates) {
+    /** @var \Drupal\migrate\Entity\MigrationInterface[] $migrations */
+    $migrations = array();
+
+    foreach ($templates as $template_id => $template) {
+      if (isset($template['builder'])) {
+        $variants = $this->builderManager
+          ->createInstance($template['builder']['plugin'], $template['builder'])
+          ->buildMigrations($template);
+      }
+      else {
+        $variants = array(Migration::create($template));
+      }
+
+      /** @var \Drupal\migrate\Entity\MigrationInterface[] $variants */
+      foreach ($variants as $variant) {
+        $variant->set('template', $template_id);
+      }
+      $migrations = array_merge($migrations, $variants);
+    }
+
+    return $migrations;
+  }
+
+}
diff --git a/core/modules/migrate/src/MigrationStorage.php b/core/modules/migrate/src/MigrationStorage.php
index 6e3ceee..26f7556 100644
--- a/core/modules/migrate/src/MigrationStorage.php
+++ b/core/modules/migrate/src/MigrationStorage.php
@@ -8,7 +8,13 @@
 namespace Drupal\migrate;
 
 use Drupal\Component\Graph\Graph;
+use Drupal\Component\Uuid\UuidInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
 use Drupal\Core\Config\Entity\ConfigEntityStorage;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Query\QueryFactoryInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Symfony\Component\DependencyInjection\ContainerInterface;
 
 /**
  * Storage for migration entities.
@@ -16,6 +22,95 @@
 class MigrationStorage extends ConfigEntityStorage implements MigrateBuildDependencyInterface {
 
   /**
+   * @var \Drupal\Core\Entity\Query\QueryFactoryInterface
+   */
+  protected $queryFactory;
+
+  /**
+   * Constructs a MigrationStorage object.
+   *
+   * @param \Drupal\Core\Entity\EntityTypeInterface $entity_type
+   *   The entity type definition.
+   * @param \Drupal\Core\Config\ConfigFactoryInterface $config_factory
+   *   The config factory service.
+   * @param \Drupal\Component\Uuid\UuidInterface $uuid_service
+   *   The UUID service.
+   * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
+   *   The language manager.
+   * @param \Drupal\Core\Entity\Query\QueryFactoryInterface $query_factory
+   *   The entity query factory service.
+   */
+  public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, QueryFactoryInterface $query_factory) {
+    parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager);
+    $this->queryFactory = $query_factory;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createInstance(ContainerInterface $container, EntityTypeInterface $entity_type) {
+    return new static(
+      $entity_type,
+      $container->get('config.factory'),
+      $container->get('uuid'),
+      $container->get('language_manager'),
+      $container->get('entity.query.config')
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function loadMultiple(array $ids = NULL) {
+    /** @var \Drupal\migrate\Entity\MigrationInterface[] $migrations */
+    $migrations = parent::loadMultiple($ids);
+
+    foreach ($migrations as $migration) {
+      $migration->set('migration_dependencies', $this->expandDependencies($migration->getMigrationDependencies()));
+    }
+    return $migrations;
+  }
+
+  /**
+   * Expands template dependencies.
+   *
+   * Migration dependencies which match the template_id:* pattern are a signal
+   * that the migration depends on every variant of template_id. This method
+   * queries for those variant IDs and splices them into the list of
+   * dependencies.
+   *
+   * @param array $dependencies
+   *   The original migration dependencies (with template IDs), organized by
+   *   group (required, optional, etc.)
+   *
+   * @return array
+   *   The expanded list of dependencies, organized by group.
+   */
+  protected function expandDependencies(array $dependencies) {
+    $expanded_dependencies = [];
+
+    foreach (array_keys($dependencies) as $group) {
+      $expanded_dependencies[$group] = [];
+
+      foreach ($dependencies[$group] as $dependency_id) {
+        if (substr($dependency_id, -2) == ':*') {
+          $template_id = substr($dependency_id, 0, -2);
+          $variants = $this->queryFactory->get($this->entityType, 'OR')
+            ->condition('id', $template_id)
+            ->condition('template', $template_id)
+            ->execute();
+          $expanded_dependencies[$group] = array_merge($expanded_dependencies[$group], $variants);
+        }
+        else {
+          $expanded_dependencies[$group][] = $dependency_id;
+        }
+      }
+    }
+
+    return $expanded_dependencies;
+  }
+
+  /**
    * {@inheritdoc}
    */
   public function buildDependencyMigration(array $migrations, array $dynamic_ids) {
diff --git a/core/modules/migrate/src/Plugin/MigrateBuilderInterface.php b/core/modules/migrate/src/Plugin/MigrateBuilderInterface.php
new file mode 100644
index 0000000..f11bdae
--- /dev/null
+++ b/core/modules/migrate/src/Plugin/MigrateBuilderInterface.php
@@ -0,0 +1,27 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\Plugin\MigrateBuilderInterface.
+ */
+
+namespace Drupal\migrate\Plugin;
+
+/**
+ * Defines the builder plugin type, which implements custom logic to generate
+ * migration entities from templates.
+ */
+interface MigrateBuilderInterface {
+
+  /**
+   * Builds migration entities based on a template.
+   *
+   * @param array $template
+   *   The parsed template.
+   *
+   * @return \Drupal\migrate\Entity\MigrationInterface[]
+   *   The unsaved migrations generated from the template.
+   */
+  public function buildMigrations(array $template);
+
+}
diff --git a/core/modules/migrate/src/Plugin/migrate/builder/BuilderBase.php b/core/modules/migrate/src/Plugin/migrate/builder/BuilderBase.php
new file mode 100644
index 0000000..1a97195
--- /dev/null
+++ b/core/modules/migrate/src/Plugin/migrate/builder/BuilderBase.php
@@ -0,0 +1,52 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\Plugin\migrate\builder\BuilderBase.
+ */
+
+namespace Drupal\migrate\Plugin\migrate\builder;
+
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\migrate\Entity\Migration;
+use Drupal\migrate\Plugin\MigrateBuilderInterface;
+
+/**
+ * Base class for builder plugins.
+ */
+abstract class BuilderBase extends PluginBase implements MigrateBuilderInterface {
+
+  /**
+   * Returns a fully initialized instance of a source plugin.
+   *
+   * @param string $plugin_id
+   *   The plugin ID.
+   * @param array $configuration
+   *   (optional) Additional configuration for the plugin.
+   *
+   * @return \Drupal\migrate\Plugin\MigrateSourceInterface
+   *   The fully initialized source plugin.
+   */
+  protected function getSourcePlugin($plugin_id, array $configuration = []) {
+    $configuration['plugin'] = $plugin_id;
+    // By default, SqlBase subclasses will try to join on a map table. But in
+    // this case we're trying to use the source plugin as a detached iterator
+    // over the source data, so we don't want to join on (or create) the map
+    // table.
+    // @see SqlBase::initializeIterator()
+    $configuration['ignore_map'] = TRUE;
+    // Source plugins are tightly coupled to migration entities, so we need
+    // to create a fake migration in order to properly initialize the plugin.
+    $values = [
+      'id' => uniqid(),
+      'source' => $configuration,
+      // Since this isn't a real migration, we don't want a real destination.
+      // The 'null' destination plugin is perfect for this! :)
+      'destination' => [
+        'plugin' => 'null',
+      ],
+    ];
+    return Migration::create($values)->getSourcePlugin();
+  }
+
+}
diff --git a/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php b/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
index 7a448f0..caa4578 100644
--- a/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
+++ b/core/modules/migrate/src/Plugin/migrate/source/SqlBase.php
@@ -138,7 +138,7 @@ protected function initializeIterator() {
       //      OR above high water).
       $conditions = $this->query->orConditionGroup();
       $condition_added = FALSE;
-      if ($this->mapJoinable()) {
+      if (empty($this->configuration['ignore_map']) && $this->mapJoinable()) {
         // Build the join to the map table. Because the source key could have
         // multiple fields, we need to build things up.
         $count = 1;
diff --git a/core/modules/migrate/tests/src/Unit/MigrateTestCase.php b/core/modules/migrate/tests/src/Unit/MigrateTestCase.php
index a6015d0..17340e9 100644
--- a/core/modules/migrate/tests/src/Unit/MigrateTestCase.php
+++ b/core/modules/migrate/tests/src/Unit/MigrateTestCase.php
@@ -28,29 +28,41 @@ protected function getMigration() {
     $this->migrationConfiguration += ['migrationClass' => 'Drupal\migrate\Entity\Migration'];
     $this->idMap = $this->getMock('Drupal\migrate\Plugin\MigrateIdMapInterface');
 
-    $this->idMap->expects($this->any())
+    $this->idMap
       ->method('getQualifiedMapTableName')
-      ->will($this->returnValue('test_map'));
+      ->willReturn('test_map');
 
     $migration = $this->getMockBuilder($this->migrationConfiguration['migrationClass'])
       ->disableOriginalConstructor()
       ->getMock();
-    $migration->expects($this->any())
-      ->method('checkRequirements')
-      ->will($this->returnValue(TRUE));
-    $migration->expects($this->any())
-      ->method('getIdMap')
-      ->will($this->returnValue($this->idMap));
+
+    $migration->method('checkRequirements')
+      ->willReturn(TRUE);
+
+    $migration->method('getIdMap')
+      ->willReturn($this->idMap);
+
+    $migration->method('getMigrationDependencies')
+      ->willReturn([
+        'required' => [],
+        'optional' => [],
+      ]);
+
     $configuration = &$this->migrationConfiguration;
-    $migration->expects($this->any())->method('get')->will($this->returnCallback(function ($argument) use (&$configuration) {
-      return isset($configuration[$argument]) ? $configuration[$argument] : '';
-    }));
-    $migration->expects($this->any())->method('set')->will($this->returnCallback(function ($argument, $value) use (&$configuration) {
+
+    $migration->method('get')
+      ->willReturnCallback(function ($argument) use (&$configuration) {
+        return isset($configuration[$argument]) ? $configuration[$argument] : '';
+      });
+
+    $migration->method('set')
+      ->willReturnCallback(function ($argument, $value) use (&$configuration) {
       $configuration[$argument] = $value;
-    }));
-    $migration->expects($this->any())
-      ->method('id')
-      ->will($this->returnValue($configuration['id']));
+    });
+
+    $migration->method('id')
+      ->willReturn($configuration['id']);
+
     return $migration;
   }
 
diff --git a/core/modules/migrate/tests/src/Unit/MigrationStorageTest.php b/core/modules/migrate/tests/src/Unit/MigrationStorageTest.php
new file mode 100644
index 0000000..a3564e2
--- /dev/null
+++ b/core/modules/migrate/tests/src/Unit/MigrationStorageTest.php
@@ -0,0 +1,137 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\migrate\Unit\MigrationStorageTest.
+ */
+
+namespace Drupal\Tests\migrate\Unit;
+
+use Drupal\Component\Uuid\UuidInterface;
+use Drupal\Core\Config\ConfigFactoryInterface;
+use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Query\QueryFactoryInterface;
+use Drupal\Core\Entity\Query\QueryInterface;
+use Drupal\Core\Language\LanguageManagerInterface;
+use Drupal\migrate\MigrationStorage;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * @coversDefaultClass \Drupal\migrate\MigrationStorage
+ * @group migrate
+ */
+class MigrationStorageTest extends UnitTestCase {
+
+  /**
+   * @var \Drupal\migrate\MigrationStorage
+   */
+  protected $storage;
+
+  /**
+   * @var \Drupal\Core\Entity\Query\QueryInterface
+   */
+  protected $query;
+
+  /**
+   * @var \ReflectionObject
+   */
+  protected $reflector;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    $this->query = $this->getMock(QueryInterface::class);
+    $this->query->method('condition')
+      ->willReturnSelf();
+
+    $query_factory = $this->getMock(QueryFactoryInterface::class);
+    $query_factory->method('get')
+      ->willReturn($this->query);
+
+    $this->storage = new MigrationStorage(
+      $this->getMock(EntityTypeInterface::class),
+      $this->getMock(ConfigFactoryInterface::class),
+      $this->getMock(UuidInterface::class),
+      $this->getMock(LanguageManagerInterface::class),
+      $query_factory
+    );
+    $this->reflector = new \ReflectionObject($this->storage);
+  }
+
+  /**
+   * Calls a protected method of $this->storage by using the reflection hack.
+   *
+   * @param string $method
+   *   The method to call.
+   * @param array $arguments
+   *   (optional) Arguments to pass.
+   *
+   * @return mixed
+   *   The return value of the called method.
+   */
+  protected function invokeMethod($method, array $arguments = []) {
+    $method = $this->reflector->getMethod($method);
+    $method->setAccessible(TRUE);
+    return $method->invokeArgs($this->storage, $arguments);
+  }
+
+  /**
+   * Tests expandDependencies() when variants exist.
+   *
+   * @covers ::expandDependencies
+   */
+  public function testExpandDependenciesWithVariants() {
+    $this->query->method('execute')
+      ->willReturn(['d6_node__page', 'd6_node__article']);
+
+    $dependencies = [
+      'required' => [
+        'd6_node:*',
+        'd6_user',
+      ],
+    ];
+    $dependencies = $this->invokeMethod('expandDependencies', [$dependencies]);
+    $this->assertSame(['d6_node__page', 'd6_node__article', 'd6_user'],  $dependencies['required']);
+  }
+
+  /**
+   * Tests expandDependencies() when no variants exist.
+   *
+   * @covers ::expandDependencies
+   */
+  public function testExpandDependenciesNoVariants() {
+    $this->query->method('execute')
+      ->willReturn([]);
+
+    $dependencies = [
+      'required' => [
+        'd6_node:*',
+        'd6_user',
+      ],
+    ];
+    $dependencies = $this->invokeMethod('expandDependencies', [$dependencies]);
+    $this->assertSame(['d6_user'],  $dependencies['required']);
+  }
+
+  /**
+   * Tests expandDependencies() when no variants exist and there are no static
+   * (non-variant) dependencies.
+   *
+   * @covers ::expandDependencies
+   */
+  public function testExpandDependenciesNoVariantsOrStaticDependencies() {
+    $this->query->method('execute')
+      ->willReturn([]);
+
+    $dependencies = [
+      'required' => [
+        'd6_node:*',
+        'd6_node_revision:*',
+      ],
+    ];
+    $dependencies = $this->invokeMethod('expandDependencies', [$dependencies]);
+    $this->assertSame([],  $dependencies['required']);
+  }
+
+}
diff --git a/core/modules/migrate_drupal/migration_templates/d6_cck_field_revision.yml b/core/modules/migrate_drupal/migration_templates/d6_cck_field_revision.yml
index fa15989..26d0726 100644
--- a/core/modules/migrate_drupal/migration_templates/d6_cck_field_revision.yml
+++ b/core/modules/migrate_drupal/migration_templates/d6_cck_field_revision.yml
@@ -14,4 +14,4 @@ destination:
 migration_dependencies:
   required:
     - d6_cck_field_values
-    - d6_node_revision
+    - d6_node_revision:*
diff --git a/core/modules/migrate_drupal/migration_templates/d6_cck_field_values.yml b/core/modules/migrate_drupal/migration_templates/d6_cck_field_values.yml
index 27027ba..83e0194 100644
--- a/core/modules/migrate_drupal/migration_templates/d6_cck_field_values.yml
+++ b/core/modules/migrate_drupal/migration_templates/d6_cck_field_values.yml
@@ -16,6 +16,6 @@ destination:
   plugin: entity:node
 migration_dependencies:
   required:
-    - d6_node
+    - d6_node:*
     - d6_field_formatter_settings
     - d6_field_instance_widget_settings
diff --git a/core/modules/migrate_drupal/migration_templates/d6_comment.yml b/core/modules/migrate_drupal/migration_templates/d6_comment.yml
index 1a41ec7..0275d1c 100644
--- a/core/modules/migrate_drupal/migration_templates/d6_comment.yml
+++ b/core/modules/migrate_drupal/migration_templates/d6_comment.yml
@@ -37,7 +37,7 @@ destination:
   plugin: entity:comment
 migration_dependencies:
   required:
-    - d6_node
+    - d6_node:*
     - d6_comment_type
     - d6_user
     - d6_comment_entity_display
diff --git a/core/modules/migrate_drupal/migration_templates/d6_field.yml b/core/modules/migrate_drupal/migration_templates/d6_field.yml
index 9ff9e24..353a97f 100644
--- a/core/modules/migrate_drupal/migration_templates/d6_field.yml
+++ b/core/modules/migrate_drupal/migration_templates/d6_field.yml
@@ -2,6 +2,9 @@ id: d6_field
 label: Drupal 6 field configuration
 migration_tags:
   - Drupal 6
+builder:
+  plugin: d6_cck_migration
+  cck_plugin_method: processField
 source:
   plugin: d6_field
   constants:
diff --git a/core/modules/migrate_drupal/migration_templates/d6_field_formatter_settings.yml b/core/modules/migrate_drupal/migration_templates/d6_field_formatter_settings.yml
index 425e7d4..32fc5d6 100644
--- a/core/modules/migrate_drupal/migration_templates/d6_field_formatter_settings.yml
+++ b/core/modules/migrate_drupal/migration_templates/d6_field_formatter_settings.yml
@@ -2,6 +2,9 @@ id: d6_field_formatter_settings
 label: Drupal 6 field formatter configuration
 migration_tags:
   - Drupal 6
+builder:
+  plugin: d6_cck_migration
+  cck_plugin_method: processFieldFormatter
 source:
   plugin: d6_field_instance_per_view_mode
   constants:
diff --git a/core/modules/migrate_drupal/migration_templates/d6_field_instance.yml b/core/modules/migrate_drupal/migration_templates/d6_field_instance.yml
index f876dfd..bdd5274 100644
--- a/core/modules/migrate_drupal/migration_templates/d6_field_instance.yml
+++ b/core/modules/migrate_drupal/migration_templates/d6_field_instance.yml
@@ -2,6 +2,9 @@ id: d6_field_instance
 label: Drupal 6 field instance configuration
 migration_tags:
   - Drupal 6
+builder:
+  plugin: d6_cck_migration
+  cck_plugin_method: processFieldInstance
 source:
   plugin: d6_field_instance
   constants:
diff --git a/core/modules/migrate_drupal/migration_templates/d6_field_instance_widget_settings.yml b/core/modules/migrate_drupal/migration_templates/d6_field_instance_widget_settings.yml
index 3299d5c..7b9255e 100644
--- a/core/modules/migrate_drupal/migration_templates/d6_field_instance_widget_settings.yml
+++ b/core/modules/migrate_drupal/migration_templates/d6_field_instance_widget_settings.yml
@@ -2,6 +2,9 @@ id: d6_field_instance_widget_settings
 label: Drupal 6 field instance widget configuration
 migration_tags:
   - Drupal 6
+builder:
+  plugin: d6_cck_migration
+  cck_plugin_method: processFieldWidget
 source:
   plugin: d6_field_instance_per_form_display
   constants:
diff --git a/core/modules/migrate_drupal/migration_templates/d6_upload.yml b/core/modules/migrate_drupal/migration_templates/d6_upload.yml
index 2d04241..217d8c0 100644
--- a/core/modules/migrate_drupal/migration_templates/d6_upload.yml
+++ b/core/modules/migrate_drupal/migration_templates/d6_upload.yml
@@ -24,4 +24,4 @@ destination:
 migration_dependencies:
   required:
     - d6_file
-    - d6_node
+    - d6_node:*
diff --git a/core/modules/migrate_drupal/src/MigrationStorage.php b/core/modules/migrate_drupal/src/MigrationStorage.php
index 9a33b36..5206930 100644
--- a/core/modules/migrate_drupal/src/MigrationStorage.php
+++ b/core/modules/migrate_drupal/src/MigrationStorage.php
@@ -12,6 +12,7 @@
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Entity\EntityStorageException;
 use Drupal\Core\Entity\EntityTypeInterface;
+use Drupal\Core\Entity\Query\QueryFactoryInterface;
 use Drupal\Core\Language\LanguageManagerInterface;
 use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
 use Drupal\migrate_drupal\Plugin\CckFieldMigrateSourceInterface;
@@ -47,11 +48,13 @@ class MigrationStorage extends BaseMigrationStorage {
    *   The UUID service.
    * @param \Drupal\Core\Language\LanguageManagerInterface $language_manager
    *   The language manager.
+   * @param \Drupal\Core\Entity\Query\QueryFactoryInterface $query_factory
+   *   The entity query factory.
    * @param \Drupal\migrate_drupal\Plugin\MigratePluginManager
    *  The cckfield plugin manager.
    */
-  public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, MigratePluginManager $cck_plugin_manager) {
-    parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager);
+  public function __construct(EntityTypeInterface $entity_type, ConfigFactoryInterface $config_factory, UuidInterface $uuid_service, LanguageManagerInterface $language_manager, QueryFactoryInterface $query_factory, MigratePluginManager $cck_plugin_manager) {
+    parent::__construct($entity_type, $config_factory, $uuid_service, $language_manager, $query_factory);
     $this->cckPluginManager = $cck_plugin_manager;
   }
 
@@ -64,6 +67,7 @@ public static function createInstance(ContainerInterface $container, EntityTypeI
       $container->get('config.factory'),
       $container->get('uuid'),
       $container->get('language_manager'),
+      $container->get('entity.query.config'),
       $container->get('plugin.manager.migrate.cckfield')
     );
   }
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/builder/d6/CckBuilder.php b/core/modules/migrate_drupal/src/Plugin/migrate/builder/d6/CckBuilder.php
new file mode 100644
index 0000000..1a48203
--- /dev/null
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/builder/d6/CckBuilder.php
@@ -0,0 +1,54 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal\Plugin\migrate\builder\d6\CckBuilder.
+ */
+
+namespace Drupal\migrate_drupal\Plugin\migrate\builder\d6;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\migrate\Plugin\migrate\builder\BuilderBase;
+use Drupal\migrate\Plugin\MigratePluginManager;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * Base class for builders which leverage cckfield plugins.
+ */
+abstract class CckBuilder extends BuilderBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * @var \Drupal\migrate\Plugin\MigratePluginManager
+   */
+  protected $cckPluginManager;
+
+  /**
+   * Constructs a CckBuilder.
+   *
+   * @param array $configuration
+   *   Plugin configuration.
+   * @param string $plugin_id
+   *   The plugin ID.
+   * @param mixed $plugin_definition
+   *   The plugin definition.
+   * @param \Drupal\migrate\Plugin\MigratePluginManager $cck_manager
+   *   The cckfield plugin manager.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigratePluginManager $cck_manager) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->cckPluginManager = $cck_manager;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('plugin.manager.migrate.cckfield')
+    );
+  }
+
+}
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/builder/d6/CckMigration.php b/core/modules/migrate_drupal/src/Plugin/migrate/builder/d6/CckMigration.php
new file mode 100644
index 0000000..d63400c
--- /dev/null
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/builder/d6/CckMigration.php
@@ -0,0 +1,53 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_drupal\Plugin\migrate\builder\d6\CckMigration.
+ */
+
+namespace Drupal\migrate_drupal\Plugin\migrate\builder\d6;
+
+use Drupal\migrate\Entity\Migration;
+
+/**
+ * @PluginID("d6_cck_migration")
+ */
+class CckMigration extends CckBuilder {
+
+  /**
+   * List of cckfield plugin IDs which have already run.
+   *
+   * @var string[]
+   */
+  protected $processedFieldTypes = [];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildMigrations(array $template) {
+    $migration = Migration::create($template);
+
+    // Loop through every field that will be migrated.
+    foreach ($migration->getSourcePlugin() as $field) {
+      $field_type = $field->getSourceProperty('type');
+
+      // Each field type should only be processed once.
+      if (in_array($field_type, $this->processedFieldTypes)) {
+        continue;
+      }
+      // Only process the current field type if a relevant cckfield plugin
+      // exists.
+      elseif ($this->cckPluginManager->hasDefinition($field_type)) {
+        $this->processedFieldTypes[] = $field_type;
+        // Allow the cckfield plugin to alter the migration as necessary so that
+        // it knows how to handle fields of this type.
+        $this->cckPluginManager
+          ->createInstance($field_type, [], $migration)
+          ->{$this->configuration['cck_plugin_method']}($migration);
+      }
+    }
+
+    return [$migration];
+  }
+
+}
diff --git a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/FieldInstance.php b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/FieldInstance.php
index 5b42504..7f1017d 100644
--- a/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/FieldInstance.php
+++ b/core/modules/migrate_drupal/src/Plugin/migrate/source/d6/FieldInstance.php
@@ -24,28 +24,12 @@ class FieldInstance extends DrupalSqlBase {
    * {@inheritdoc}
    */
   public function query() {
-    $query = $this->select('content_node_field_instance', 'cnfi')
-      ->fields('cnfi', array(
-        'field_name',
-        'type_name',
-        'weight',
-        'label',
-        'widget_type',
-        'widget_settings',
-        'display_settings',
-        'description',
-        'widget_module',
-        'widget_active',
-        'description',
-      ))
-      ->fields('cnf', array(
-        'required',
-        'active',
-        'global_settings',
-      ));
-
+    $query = $this->select('content_node_field_instance', 'cnfi')->fields('cnfi');
+    if (isset($this->configuration['node_type'])) {
+      $query->condition('cnfi.type_name', $this->configuration['node_type']);
+    }
     $query->join('content_node_field', 'cnf', 'cnf.field_name = cnfi.field_name');
-    $query->orderBy('weight');
+    $query->fields('cnf');
 
     return $query;
   }
diff --git a/core/modules/node/migration_templates/d6_node.yml b/core/modules/node/migration_templates/d6_node.yml
index 947a3cf..76b34d8 100644
--- a/core/modules/node/migration_templates/d6_node.yml
+++ b/core/modules/node/migration_templates/d6_node.yml
@@ -2,6 +2,8 @@ id: d6_node
 label: Drupal 6 nodes
 migration_tags:
   - Drupal 6
+builder:
+  plugin: d6_node
 source:
   plugin: d6_node
 process:
diff --git a/core/modules/node/migration_templates/d6_node_revision.yml b/core/modules/node/migration_templates/d6_node_revision.yml
index 0b1a478..9ba85d3 100644
--- a/core/modules/node/migration_templates/d6_node_revision.yml
+++ b/core/modules/node/migration_templates/d6_node_revision.yml
@@ -2,6 +2,8 @@ id: d6_node_revision
 label: Drupal 6 node revisions
 migration_tags:
   - Drupal 6
+builder:
+  plugin: d6_node
 source:
   plugin: d6_node_revision
 process:
@@ -39,4 +41,4 @@ destination:
   plugin: entity_revision:node
 migration_dependencies:
   required:
-    - d6_node
+    - d6_node:*
diff --git a/core/modules/node/src/Plugin/migrate/builder/d6/Node.php b/core/modules/node/src/Plugin/migrate/builder/d6/Node.php
new file mode 100644
index 0000000..6bc0966
--- /dev/null
+++ b/core/modules/node/src/Plugin/migrate/builder/d6/Node.php
@@ -0,0 +1,75 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Plugin\migrate\builder\d6\Node.
+ */
+
+namespace Drupal\node\Plugin\migrate\builder\d6;
+
+use Drupal\migrate\Entity\Migration;
+use Drupal\migrate\Entity\MigrationInterface;
+use Drupal\migrate_drupal\Plugin\migrate\builder\d6\CckBuilder;
+
+/**
+ * @PluginID("d6_node")
+ */
+class Node extends CckBuilder {
+
+  /**
+   * Already-instantiated cckfield plugins, keyed by ID.
+   *
+   * @var \Drupal\migrate_drupal\Plugin\MigrateCckFieldInterface[]
+   */
+  protected $cckPluginCache = [];
+
+  /**
+   * Gets a cckfield plugin instance.
+   *
+   * @param string $field_type
+   *   The field type (plugin ID).
+   * @param \Drupal\migrate\Entity\MigrationInterface|NULL $migration
+   *   The migration, if any.
+   *
+   * @return \Drupal\migrate_drupal\Plugin\MigrateCckFieldInterface
+   *   The cckfield plugin instance.
+   */
+  protected function getCckPlugin($field_type, MigrationInterface $migration = NULL) {
+    if (empty($this->cckPluginCache[$field_type])) {
+      $this->cckPluginCache[$field_type] = $this->cckPluginManager->createInstance($field_type, [], $migration);
+    }
+    return $this->cckPluginCache[$field_type];
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildMigrations(array $template) {
+    $migrations = [];
+
+    foreach ($this->getSourcePlugin('d6_node_type', $template['source']) as $row) {
+      $node_type = $row->getSourceProperty('type');
+      $values = $template;
+      $values['id'] = $template['id'] . '__' . $node_type;
+      $migration = Migration::create($values);
+
+      $fields = $this->getSourcePlugin('d6_field_instance', ['node_type' => $node_type] + $template['source']);
+      foreach ($fields as $field) {
+        $data = $field->getSource();
+
+        if ($this->cckPluginManager->hasDefinition($data['type'])) {
+          $this->getCckPlugin($data['type'])
+            ->processCckFieldValues($migration, $data['field_name'], $data);
+        }
+        else {
+          $migration->setProcessOfProperty($data['field_name'], $data['field_name']);
+        }
+      }
+
+      $migrations[] = $migration;
+    }
+
+    return $migrations;
+  }
+
+}
diff --git a/core/modules/node/src/Tests/Migrate/d6/MigrateNodeBuilderTest.php b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeBuilderTest.php
new file mode 100644
index 0000000..168b856
--- /dev/null
+++ b/core/modules/node/src/Tests/Migrate/d6/MigrateNodeBuilderTest.php
@@ -0,0 +1,69 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\node\Tests\Migrate\d6\MigrateNodeBuilderTest.
+ */
+
+namespace Drupal\node\Tests\Migrate\d6;
+
+use Drupal\migrate_drupal\Tests\d6\MigrateDrupal6TestBase;
+
+/**
+ * @group node
+ */
+class MigrateNodeBuilderTest extends MigrateDrupal6TestBase {
+
+  public static $modules = ['migrate', 'migrate_drupal', 'node'];
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+    $this->loadDumps([
+      'ContentNodeField.php',
+      'ContentNodeFieldInstance.php',
+      'NodeType.php',
+    ]);
+  }
+
+  /**
+   * Tests creating migrations from a template, using a builder plugin.
+   */
+  public function testCreateMigrations() {
+    $templates = [
+      'd6_node' => [
+        'builder' => [
+          'plugin' => 'd6_node',
+        ],
+        'source' => [
+          'plugin' => 'd6_node',
+        ],
+        'process' => [
+          'nid' => 'nid',
+          'vid' => 'vid',
+          'uid' => 'uid',
+        ],
+        'destination' => [
+          'plugin' => 'entity:node',
+        ],
+      ],
+    ];
+
+    $migrations = \Drupal::service('migrate.migration_builder')->createMigrations($templates);
+    $this->assertIdentical(11, count($migrations));
+    $this->assertIdentical('d6_node__article', $migrations[0]->id());
+    $this->assertIdentical('d6_node__company', $migrations[1]->id());
+    $this->assertIdentical('d6_node__employee', $migrations[2]->id());
+    $this->assertIdentical('d6_node__event', $migrations[3]->id());
+    $this->assertIdentical('d6_node__page', $migrations[4]->id());
+    $this->assertIdentical('d6_node__sponsor', $migrations[5]->id());
+    $this->assertIdentical('d6_node__story', $migrations[6]->id());
+    $this->assertIdentical('d6_node__test_event', $migrations[7]->id());
+    $this->assertIdentical('d6_node__test_page', $migrations[8]->id());
+    $this->assertIdentical('d6_node__test_planet', $migrations[9]->id());
+    $this->assertIdentical('d6_node__test_story', $migrations[10]->id());
+  }
+
+}
diff --git a/core/modules/taxonomy/migration_templates/d6_term_node.yml b/core/modules/taxonomy/migration_templates/d6_term_node.yml
index 4eaf935..ee7f830 100644
--- a/core/modules/taxonomy/migration_templates/d6_term_node.yml
+++ b/core/modules/taxonomy/migration_templates/d6_term_node.yml
@@ -2,6 +2,8 @@ id: d6_term_node
 label: Drupal 6 term/node relationships
 migration_tags:
   - Drupal 6
+builder:
+  plugin: d6_term_node
 load:
   plugin: d6_term_node
 
@@ -19,4 +21,4 @@ migration_dependencies:
   required:
     - d6_vocabulary_entity_display
     - d6_vocabulary_entity_form_display
-    - d6_node
+    - d6_node:*
diff --git a/core/modules/taxonomy/migration_templates/d6_term_node_revision.yml b/core/modules/taxonomy/migration_templates/d6_term_node_revision.yml
index 319d000..d904d41 100644
--- a/core/modules/taxonomy/migration_templates/d6_term_node_revision.yml
+++ b/core/modules/taxonomy/migration_templates/d6_term_node_revision.yml
@@ -2,6 +2,8 @@ id: d6_term_node_revision
 label: Drupal 6 term/node relationship revisions
 migration_tags:
   - Drupal 6
+builder:
+  plugin: d6_term_node
 load:
   plugin: d6_term_node
   bundle_migration: d6_vocabulary_field
@@ -18,5 +20,5 @@ destination:
   plugin: entity_revision:node
 migration_dependencies:
   required:
-    - d6_term_node
-    - d6_node_revision
+    - d6_term_node:*
+    - d6_node_revision:*
diff --git a/core/modules/taxonomy/src/Plugin/migrate/builder/d6/TermNode.php b/core/modules/taxonomy/src/Plugin/migrate/builder/d6/TermNode.php
new file mode 100644
index 0000000..6ef3e00
--- /dev/null
+++ b/core/modules/taxonomy/src/Plugin/migrate/builder/d6/TermNode.php
@@ -0,0 +1,107 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\taxonomy\Plugin\migrate\builder\d6\TermNode.
+ */
+
+namespace Drupal\taxonomy\Plugin\migrate\builder\d6;
+
+use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
+use Drupal\migrate\Entity\Migration;
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate\MigrateMessage;
+use Drupal\migrate\MigrateTemplateStorage;
+use Drupal\migrate\Plugin\migrate\builder\BuilderBase;
+use Drupal\migrate\Row;
+use Symfony\Component\DependencyInjection\ContainerInterface;
+
+/**
+ * @PluginID("d6_term_node")
+ */
+class TermNode extends BuilderBase implements ContainerFactoryPluginInterface {
+
+  /**
+   * @var \Drupal\migrate\MigrateTemplateStorage
+   */
+  protected $templateStorage;
+
+  /**
+   * Constructs a TermNode builder.
+   *
+   * @param array $configuration
+   *   Plugin configuration.
+   * @param string $plugin_id
+   *   The plugin ID.
+   * @param mixed $plugin_definition
+   *   The plugin definition.
+   * @param \Drupal\migrate\MigrateTemplateStorage $template_storage
+   *   The migration template storage handler.
+   */
+  public function __construct(array $configuration, $plugin_id, $plugin_definition, MigrateTemplateStorage $template_storage) {
+    parent::__construct($configuration, $plugin_id, $plugin_definition);
+    $this->templateStorage = $template_storage;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function create(ContainerInterface $container, array $configuration, $plugin_id, $plugin_definition) {
+    return new static(
+      $configuration,
+      $plugin_id,
+      $plugin_definition,
+      $container->get('migrate.template_storage')
+    );
+  }
+
+  /**
+   * Builds a map of source vocabulary IDs to expected destination IDs.
+   *
+   * @param array $source
+   *   Additional configuration for the d6_taxonomy_vocabulary source.
+   *
+   * @return array
+   *   The vid map. The keys are the source IDs and the values are the
+   *   (expected) destination IDs.
+   */
+  protected function getVocabularyIdMap(array $source) {
+    $map = [];
+
+    $template = $this->templateStorage->getTemplateByName('d6_taxonomy_vocabulary');
+    $template['source'] += $source;
+
+    $migration = Migration::create($template);
+    $executable = new MigrateExecutable($migration, new MigrateMessage());
+    // Only process the destination ID properties.
+    $process = array_intersect_key($template['process'], $migration->getDestinationPlugin()->getIds());
+
+    foreach ($migration->getSourcePlugin()->getIterator() as $source_row) {
+      $row = new Row($source_row, $source_row);
+      // Process the row to generate the expected destination ID.
+      $executable->processRow($row, $process);
+      $map[$row->getSourceProperty('vid')] = $row->getDestinationProperty('vid');
+    }
+
+    return $map;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildMigrations(array $template) {
+    $migrations = [];
+
+    foreach ($this->getVocabularyIdMap($template['source']) as $source_vid => $destination_vid) {
+      $values = $template;
+      $values['id'] .= '__' . $source_vid;
+      $values['source']['vid'] = $source_vid;
+      $migration = Migration::create($values);
+      $migration->setProcessOfProperty($destination_vid, 'tid');
+      $migrations[] = $migration;
+    }
+
+    return $migrations;
+  }
+
+}
