diff --git a/core/modules/migrate/lib/Drupal/migrate/Plugin/migrate/process/Iterator.php b/core/modules/migrate/lib/Drupal/migrate/Plugin/migrate/process/Iterator.php
new file mode 100644
index 0000000000000000000000000000000000000000..34206e6677632be0edb9cfb54a9084cc85283338
--- /dev/null
+++ b/core/modules/migrate/lib/Drupal/migrate/Plugin/migrate/process/Iterator.php
@@ -0,0 +1,59 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate\Plugin\migrate\process\Iterator.
+ */
+
+namespace Drupal\migrate\Plugin\migrate\process;
+
+use Drupal\Core\Plugin\PluginBase;
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate\Plugin\MigrateProcessInterface;
+use Drupal\migrate\Row;
+
+/**
+ * This plugin iterates and processes an array.
+ *
+ * @see https://drupal.org/node/2135345
+ *
+ * @PluginId("iterator")
+ */
+class Iterator extends PluginBase implements MigrateProcessInterface {
+
+  /**
+   * Runs a process pipeline on each destination property per list item.
+   */
+  public function transform($value, MigrateExecutable $migrate_executable, Row $row, $destination_property) {
+    $return = array();
+    foreach ($value as $key => $new_value) {
+      $new_row = new Row($new_value, array());
+      $migrate_executable->processRow($new_row, $this->configuration['process']);
+      $destination = $new_row->getDestination();
+      if (array_key_exists('key', $this->configuration)) {
+        $key = $this->transformKey($key, $migrate_executable, $new_row);
+      }
+      $return[$key] = $destination;
+    }
+    return $return;
+  }
+
+  /**
+   * Runs the process pipeline for the current key.
+   *
+   * @param string|int $key
+   *   The current key.
+   * @param \Drupal\migrate\MigrateExecutable $migrate_executable
+   *   The migrate executable helper class.
+   * @param \Drupal\migrate\Row $row
+   *   The current row after processing.
+   * @return mixed
+   *   The transformed key.
+   */
+  protected function transformKey($key, MigrateExecutable $migrate_executable, Row $row) {
+    $process = array('key' => $this->configuration['key']);
+    $migrate_executable->processRow($row, $process, $key);
+    return $row->getDestinationProperty('key');
+  }
+
+}
diff --git a/core/modules/migrate/tests/Drupal/migrate/Tests/process/IteratorTest.php b/core/modules/migrate/tests/Drupal/migrate/Tests/process/IteratorTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..39aeb545f9c968cbb8a48c3313499fcb43d0571e
--- /dev/null
+++ b/core/modules/migrate/tests/Drupal/migrate/Tests/process/IteratorTest.php
@@ -0,0 +1,101 @@
+<?php
+/**
+ * @file
+ * Contains Drupal\migrate\Tests\process\IteratorTest.
+ */
+
+namespace Drupal\migrate\Tests\process;
+
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate\Plugin\migrate\process\Get;
+use Drupal\migrate\Plugin\migrate\process\Iterator;
+use Drupal\migrate\Row;
+use Drupal\migrate\Tests\MigrateTestCase;
+
+/**
+ * Tests the iterator process plugin.
+ *
+ * @group migrate
+ * @group Drupal
+ */
+class IteratorTest extends MigrateTestCase {
+
+  /**
+   * The iterator plugin being tested.
+   *
+   * @var \Drupal\migrate\Plugin\migrate\process\TestIterator
+   */
+  protected $plugin;
+
+  /**
+   * @var array
+   */
+  protected $migrationConfiguration = array(
+    'id' => 'test',
+  );
+
+  /**
+   * @var bool
+   */
+  protected $mapJoinable = FALSE;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'Iterator process plugin',
+      'description' => 'Tests the iterator process plugin.',
+      'group' => 'Migrate',
+    );
+  }
+
+  /**
+   * Tests the iterator process plugin.
+   */
+  function testIterator() {
+    $migration = $this->getMigration();
+    // Set up the properties for the iterator.
+    $configuration = array(
+      'process' => array(
+        'foo' => 'source_foo',
+        'id' => 'source_id',
+      ),
+      'key' => '@id',
+    );
+    $plugin = new Iterator($configuration, 'iterator', array());
+    // Manually create the plugins. Migration::getProcessPlugins does this
+    // normally but the plugin system is not available.
+    foreach ($configuration['process'] as $destination => $source) {
+      $iterator_plugins[$destination][] = new Get(array('source' => $source), 'get', array());
+    }
+    $migration->expects($this->at(1))
+      ->method('getProcessPlugins')
+      ->will($this->returnValue($iterator_plugins));
+    // Set up the key plugins.
+    $key_plugin['key'][] = new Get(array('source' => '@id'), 'get', array());
+    $migration->expects($this->at(2))
+      ->method('getProcessPlugins')
+      ->will($this->returnValue($key_plugin));
+    $migrate_executable = new MigrateExecutable($migration, $this->getMock('Drupal\migrate\MigrateMessageInterface'));
+
+    // The current value of the pipeline.
+    $current_value = array(
+      array(
+        'source_foo' => 'test',
+        'source_id' => 42,
+      ),
+    );
+    // This is not used but the interface requires it, so create an empty row.
+    $row = new Row(array(), array());
+
+    // After transformation, check to make sure that source_foo and source_id's
+    // values ended up in the proper destinations, and that the value of the
+    // key (@id) is the same as the destination ID (42).
+    $new_value = $plugin->transform($current_value, $migrate_executable, $row, 'test');
+    $this->assertSame(count($new_value), 1);
+    $this->assertSame(count($new_value[42]), 2);
+    $this->assertSame($new_value[42]['foo'], 'test');
+    $this->assertSame($new_value[42]['id'], 42);
+  }
+}