diff --git a/migrate_example/config/migrate.migration.migrate_example_content.yml b/migrate_example/config/migrate.migration.migrate_example_content.yml
new file mode 100644
index 0000000..66ca514
--- /dev/null
+++ b/migrate_example/config/migrate.migration.migrate_example_content.yml
@@ -0,0 +1,22 @@
+id: migrate_example_content
+source:
+  plugin: migrate_example_content
+destination:
+  plugin: entity
+  entity_type: node
+  type: page
+process:
+  title: subject
+  # @todo The 'body' field to be migrated when https://drupal.org/node/2164451.
+  # body: text
+  uid:
+    -
+      plugin: migration
+      migration: migrate_example_people
+      source:
+        - author
+  created:
+    -
+      plugin: callback
+      callable: strtotime
+      source: date
diff --git a/migrate_example/config/migrate.migration.migrate_example_people.yml b/migrate_example/config/migrate.migration.migrate_example_people.yml
new file mode 100644
index 0000000..7020954
--- /dev/null
+++ b/migrate_example/config/migrate.migration.migrate_example_people.yml
@@ -0,0 +1,26 @@
+id: migrate_example_people
+source:
+  plugin: migrate_example_people
+destination:
+  plugin: entity_user
+  md5_passwords: true
+process:
+  name:
+    -
+      plugin: concat
+      delimiter: .
+      source:
+        - first_name
+        - last_name
+    -
+      plugin: callback
+      callable:
+        - '\Drupal\Component\Utility\Unicode'
+        - strtolower
+  mail: email
+  pass: pass
+  roles:
+    -
+      plugin: explode
+      delimiter: ';'
+      source: groups
diff --git a/migrate_example/config/migrate.migration.migrate_example_user_role.yml b/migrate_example/config/migrate.migration.migrate_example_user_role.yml
new file mode 100644
index 0000000..b16582c
--- /dev/null
+++ b/migrate_example/config/migrate.migration.migrate_example_user_role.yml
@@ -0,0 +1,16 @@
+id: migrate_example_user_role
+source:
+  plugin: migrate_example_user_role
+destination:
+  plugin: entity
+  entity_type: user_role
+process:
+  id:
+    -
+      plugin: machine_name
+      source: group
+    -
+      plugin: dedupe_entity
+      entity_type: user_role
+      field: id
+  label: group
diff --git a/migrate_example/lib/Drupal/migrate_example/Controller/MigrateExampleController.php b/migrate_example/lib/Drupal/migrate_example/Controller/MigrateExampleController.php
new file mode 100644
index 0000000..b6ed5dc
--- /dev/null
+++ b/migrate_example/lib/Drupal/migrate_example/Controller/MigrateExampleController.php
@@ -0,0 +1,40 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_example\Controller\MigrateExampleController.
+ */
+
+namespace Drupal\migrate_example\Controller;
+
+/**
+ * Controller routines for migrate_example.
+ *
+ * @ingroup migrate_example
+ */
+class MigrateExampleController {
+
+  /**
+   * A simple controller method to explain what this module is about.
+   */
+  public function description() {
+    $markup = '';
+
+    $markup .= '<p>' . t(' A basic example of defining a migration through configuration YAML files and custom source and process plugins.') . '</p>';
+    $markup .= '<p>' . t("In this example we are performing three migrations: roles, users, content. The source of the migration is proprietary hypothetical CMS into Drupal 8, using the migrate API provided by the core Migrate module. The biggest part of the migration is defined in the migration YAML files, placed under <code>config/</code>' directory.") . '</p>';
+    $markup .= '<p>' . t("However, migrate config files are not enough because we still need to implement some migration plugins. The module implements a source plugin for each migration and a process plugin for transforming the value to be imported. Sandbox data is defined in migrate_example.install file and it's used in tests provided by the module.") . '</p>';
+    $markup .= '<h3>' . t("Migrate examples provided by the module:") . '</h3>';
+    $markup .= '<ul>';
+    $markup .= '<li>' . t("<code>migrate_example_user_role</code>: Migrates the source groups into Drupal roles. The source doesn't keep a separate table for groups, we are processing them from the people table.") . '</li>';
+    $markup .= '<li>' . t("<code>migrate_example_people</code>: Migrates the list of users into Drupal. This migration request a simple custom process plugin 'explode' that expands the list of user groups from a semicolon delimited string to an array.") . '</li>';
+   $markup .= '<li>' . t("<code>migrate_example_content</code>: Migrates the source articles into destination node entities of type <code>page</code>. Note that the article author is migrated using the map of the previous users migration.") . '</li>';
+    $markup .= '</ul>';
+    $markup .= '<h3>' . t("How to test?") . '</h3>';
+    $markup .= '<p>' . t("There are three simpletest tests provided for each migration or you can simply enable the module and run manually the migrations.") . '</p>';
+
+    // @todo The 'body' field to be migrated in https://drupal.org/node/2164451.
+
+    return array('#markup' => $markup);
+  }
+
+}
diff --git a/migrate_example/lib/Drupal/migrate_example/Plugin/migrate/process/Explode.php b/migrate_example/lib/Drupal/migrate_example/Plugin/migrate/process/Explode.php
new file mode 100644
index 0000000..5e5aaae
--- /dev/null
+++ b/migrate_example/lib/Drupal/migrate_example/Plugin/migrate/process/Explode.php
@@ -0,0 +1,30 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_example\Plugin\migrate\process\Explode.
+ */
+
+namespace Drupal\migrate_example\Plugin\migrate\process;
+
+use Drupal\migrate\MigrateExecutable;
+use Drupal\migrate\ProcessPluginBase;
+use Drupal\migrate\Row;
+
+/**
+ * Explodes a strings by a delimiter.
+ *
+ * @MigrateProcessPlugin(
+ *   id = "explode",
+ * )
+ */
+class Explode extends ProcessPluginBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function transform($value, MigrateExecutable $migrate_executable, Row $row, $destination_property) {
+    return explode($this->configuration['delimiter'], $value);
+  }
+
+}
diff --git a/migrate_example/lib/Drupal/migrate_example/Plugin/migrate/source/Base.php b/migrate_example/lib/Drupal/migrate_example/Plugin/migrate/source/Base.php
new file mode 100644
index 0000000..9006329
--- /dev/null
+++ b/migrate_example/lib/Drupal/migrate_example/Plugin/migrate/source/Base.php
@@ -0,0 +1,28 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_example\Plugin\migrate\source\Base.
+ */
+
+namespace Drupal\migrate_example\Plugin\migrate\source;
+
+use Drupal\Core\Database\Database;
+use Drupal\migrate\Plugin\migrate\source\SqlBase;
+
+/**
+ * Migrate Example base class for users and content sources.
+ */
+abstract class Base extends SqlBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getDatabase() {
+    if (!isset($this->database)) {
+      $this->database = Database::getConnection('default', 'default');
+    }
+    return $this->database;
+  }
+
+}
diff --git a/migrate_example/lib/Drupal/migrate_example/Plugin/migrate/source/Content.php b/migrate_example/lib/Drupal/migrate_example/Plugin/migrate/source/Content.php
new file mode 100644
index 0000000..5fcb80c
--- /dev/null
+++ b/migrate_example/lib/Drupal/migrate_example/Plugin/migrate/source/Content.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_example\Plugin\migrate\source\Content.
+ */
+
+namespace Drupal\migrate_example\Plugin\migrate\source;
+
+/**
+ * Migrate Example content source from database.
+ *
+ * @PluginId("migrate_example_content")
+ */
+class Content extends Base {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    return array('article_id' => array('type' => 'integer'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    return array(
+      'article_id' => $this->t('Article ID'),
+      'subject' => $this->t('Subject'),
+      'text' => $this->t('Article body text'),
+      'author' => $this->t('Author user ID.'),
+      'date' => $this->t('Article release date'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    $fields = array_keys($this->fields());
+    return $this->select('migrate_example_content', 'c')
+      ->fields('c', $fields);
+  }
+
+}
diff --git a/migrate_example/lib/Drupal/migrate_example/Plugin/migrate/source/People.php b/migrate_example/lib/Drupal/migrate_example/Plugin/migrate/source/People.php
new file mode 100644
index 0000000..b0bb3ec
--- /dev/null
+++ b/migrate_example/lib/Drupal/migrate_example/Plugin/migrate/source/People.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_example\Plugin\migrate\source\People.
+ */
+
+namespace Drupal\migrate_example\Plugin\migrate\source;
+
+/**
+ * Migrate Example role source from database.
+ *
+ * @PluginId("migrate_example_people")
+ */
+class People extends Base {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    return array('id' => array('type' => 'integer'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    return array(
+      'id' => $this->t('User ID'),
+      'first_name' => $this->t('First name'),
+      'last_name' => $this->t('Last name'),
+      'email' => $this->t('E-mail'),
+      'pass' => $this->t('MD5 hashed password'),
+      'groups' => $this->t('User groups'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function query() {
+    return $this->select('migrate_example_people', 'p')
+      ->fields('p', array('id', 'first_name', 'last_name', 'email', 'pass', 'groups'));
+  }
+
+}
diff --git a/migrate_example/lib/Drupal/migrate_example/Plugin/migrate/source/Role.php b/migrate_example/lib/Drupal/migrate_example/Plugin/migrate/source/Role.php
new file mode 100644
index 0000000..6c002b3
--- /dev/null
+++ b/migrate_example/lib/Drupal/migrate_example/Plugin/migrate/source/Role.php
@@ -0,0 +1,89 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_example\Plugin\migrate\source\Role.
+ */
+
+namespace Drupal\migrate_example\Plugin\migrate\source;
+
+use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
+
+/**
+ * Migrate Example role source from database.
+ *
+ * @PluginId("migrate_example_user_role")
+ */
+class Role extends SourcePluginBase {
+
+  /**
+   * Iterator.
+   *
+   * @var \IteratorIterator
+   */
+  protected $iterator;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIds() {
+    return array('group' => array('type' => 'string'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function __toString() {
+    return (string) $this->query();
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function fields() {
+    return array(
+      'group' => $this->t('Group'),
+    );
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getIterator() {
+    if (!isset($this->iterator)) {
+      $people = $this->query()->execute()->fetchCol();
+
+      $items = array();
+      foreach ($people as $groups) {
+        $groups = explode(';', $groups);
+        foreach ($groups as $group) {
+          if (!isset($items[$group])) {
+            $items[$group] = array(
+              'group' => $group,
+            );
+          }
+        }
+      }
+      $this->iterator = new \IteratorIterator(new \ArrayIterator($items));
+    }
+
+    return $this->iterator;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function count() {
+    $this->getIterator()->count();
+  }
+
+  /**
+   * Gets the source user records.
+   *
+   * @returns \Drupal\Core\Database\Query\SelectInterface
+   */
+  protected function query() {
+    return db_select('migrate_example_people', 'p')
+      ->fields('p', array('groups'));
+  }
+}
diff --git a/migrate_example/lib/Drupal/migrate_example/Tests/MigrateExampleContentTest.php b/migrate_example/lib/Drupal/migrate_example/Tests/MigrateExampleContentTest.php
new file mode 100644
index 0000000..7196648
--- /dev/null
+++ b/migrate_example/lib/Drupal/migrate_example/Tests/MigrateExampleContentTest.php
@@ -0,0 +1,64 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_example\Tests\MigrateExampleContentTest.
+ */
+
+namespace Drupal\migrate_example\Tests;
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\migrate\MigrateExecutable;
+
+class MigrateExampleContentTest extends MigrateExampleTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name'  => 'Migrate Content Example',
+      'description'  => 'Example of migrating content from a proprietary legacy system.',
+      'group' => 'Examples',
+    );
+  }
+
+  function testContent() {
+    // Migrate users prior to content migration.
+    $user_migration = entity_load('migration', 'migrate_example_people');
+    $executable = new MigrateExecutable($user_migration, $this);
+    $executable->import();
+
+    // Migrate content.
+    $migration = entity_load('migration', 'migrate_example_content');
+    $executable = new MigrateExecutable($migration, $this);
+    $executable->import();
+
+    $source = db_select('migrate_example_content', 'c')
+      ->fields('c', array('article_id', 'subject', 'text', 'author', 'date'))
+      ->execute()
+      ->fetchAllAssoc('article_id');
+
+    foreach ($source as $article_id => $content) {
+      // Get the destination node ID from the migration map.
+      $nid = $migration->getIdMap()->lookupDestinationId(array($article_id));
+      $nid = reset($nid);
+
+      $node = node_load($nid);
+
+      // Assertions.
+      $this->assertEqual($node->title->value, $content->subject);
+
+      // @todo The 'body' field to be migrated only when
+      //   https://drupal.org/node/2164451.
+      // $this->assertEqual($node->body->value, $content->text);
+
+      $uid = $user_migration->getIdMap()->lookupDestinationId(array($content->author));
+      $uid = reset($uid);
+      $this->assertEqual($node->uid->value, $uid);
+      $this->assertEqual($node->getCreatedTime(), strtotime($content->date));
+    }
+
+  }
+
+}
diff --git a/migrate_example/lib/Drupal/migrate_example/Tests/MigrateExamplePeopleTest.php b/migrate_example/lib/Drupal/migrate_example/Tests/MigrateExamplePeopleTest.php
new file mode 100644
index 0000000..aea4b5d
--- /dev/null
+++ b/migrate_example/lib/Drupal/migrate_example/Tests/MigrateExamplePeopleTest.php
@@ -0,0 +1,61 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_example\Tests\MigrateExamplePeopleTest.
+ */
+
+namespace Drupal\migrate_example\Tests;
+
+use Drupal\Component\Utility\Unicode;
+use Drupal\migrate\MigrateExecutable;
+
+class MigrateExamplePeopleTest extends MigrateExampleTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name'  => 'Migrate Users Example',
+      'description'  => 'Example of migrating users from a proprietary legacy system.',
+      'group' => 'Examples',
+    );
+  }
+
+  function testUsers() {
+
+    $migration = entity_load('migration', 'migrate_example_people');
+    $executable = new MigrateExecutable($migration, $this);
+    $executable->import();
+
+    $source = db_select('migrate_example_people', 'p')
+      ->fields('p', array('id', 'first_name', 'last_name', 'email', 'pass', 'plain_pass', 'groups'))
+      ->execute()
+      ->fetchAllAssoc('id');
+
+    foreach ($source as $id => $person) {
+      // Get the destination user ID.
+      $uid = $migration->getIdMap()->lookupDestinationId(array($id));
+      $uid = reset($uid);
+
+      $user = entity_load('user', $uid);
+
+      // Assertions.
+      $name = Unicode::strtolower($person->first_name . '.' . $person->last_name);
+      $this->assertEqual($user->label(), $name);
+      $this->assertEqual($user->mail->value, $person->email);
+
+      $groups = explode(';', $person->groups);
+      $diff = array_diff($user->getRoles(), $groups);
+      $this->assertEqual($diff, array('authenticated'));
+
+      // Login using the UI.
+      $this->drupalPostForm('user/login', array('name' => $name, 'pass' => $person->plain_pass), t('Log in'));
+      $this->assertNoRaw(t('Sorry, unrecognized username or password. <a href="@password">Have you forgotten your password?</a>', array('@password' => url('user/password', array('query' => array('name' => $name))))));
+      $this->drupalLogout();
+    }
+
+  }
+
+}
diff --git a/migrate_example/lib/Drupal/migrate_example/Tests/MigrateExampleTestBase.php b/migrate_example/lib/Drupal/migrate_example/Tests/MigrateExampleTestBase.php
new file mode 100644
index 0000000..65b702d
--- /dev/null
+++ b/migrate_example/lib/Drupal/migrate_example/Tests/MigrateExampleTestBase.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_example\Tests\MigrateExampleTestBase.
+ */
+
+namespace Drupal\migrate_example\Tests;
+
+use Drupal\migrate\MigrateMessageInterface;
+use Drupal\simpletest\WebTestBase;
+
+class MigrateExampleTestBase extends WebTestBase implements MigrateMessageInterface  {
+
+  public static $modules = array('migrate_example');
+
+  /**
+   * {@inheritdoc}
+   */
+  public function display($message, $type = 'status') {
+    if ($type == 'status') {
+      $this->pass($message);
+    }
+    else {
+      $this->fail($message);
+    }
+  }
+
+}
diff --git a/migrate_example/lib/Drupal/migrate_example/Tests/MigrateExampleUserRoleTest.php b/migrate_example/lib/Drupal/migrate_example/Tests/MigrateExampleUserRoleTest.php
new file mode 100644
index 0000000..29dd302
--- /dev/null
+++ b/migrate_example/lib/Drupal/migrate_example/Tests/MigrateExampleUserRoleTest.php
@@ -0,0 +1,39 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\migrate_example\Tests\MigrateExampleUserRoleTest.
+ */
+
+namespace Drupal\migrate_example\Tests;
+
+use Drupal\migrate\MigrateExecutable;
+
+class MigrateExampleUserRoleTest extends MigrateExampleTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name'  => 'Migrate User Roles Example',
+      'description'  => 'Example of migrating user groups/roles from a proprietary legacy system.',
+      'group' => 'Examples',
+    );
+  }
+
+  function testUserRole() {
+
+    /** @var \Drupal\migrate\entity\Migration $migration */
+    $migration = entity_load('migration', 'migrate_example_user_role');
+    $executable = new MigrateExecutable($migration, $this);
+    $executable->import();
+
+    foreach (array('staff', 'management', 'shareholder') as $rid) {
+      $role = entity_load('user_role', $rid);
+      $this->assertEqual($role->id(), $rid);
+      $this->assertEqual(array($rid), $migration->getIdMap()->lookupDestinationId(array($rid)));
+    }
+  }
+
+}
diff --git a/migrate_example/migrate_example.info.yml b/migrate_example/migrate_example.info.yml
new file mode 100755
index 0000000..7fbfa12
--- /dev/null
+++ b/migrate_example/migrate_example.info.yml
@@ -0,0 +1,7 @@
+name: Migrate Example
+description: The simplest example of implementing a migration.
+package: Example modules
+type: module
+core: 8.x
+dependencies:
+  - migrate
diff --git a/migrate_example/migrate_example.install b/migrate_example/migrate_example.install
new file mode 100644
index 0000000..540c1c7
--- /dev/null
+++ b/migrate_example/migrate_example.install
@@ -0,0 +1,197 @@
+<?php
+
+/**
+ * @file
+ * Install, update and uninstall functions for the migrate_example module.
+ */
+
+/**
+ * Implements hook_install().
+ *
+ * Creates some default entries on this module custom table.
+ *
+ * @see hook_install()
+ * @ingroup migrate_example
+ */
+function migrate_example_install() {
+  // Outside of the .install file we would use drupal_write_record() to populate
+  // the database, but it cannot be used here, so we'll use db_insert(). Add a
+  // default entry.
+  $people = array(
+    array(
+      'id' => mt_rand(1, 100),
+      'first_name' => 'John',
+      'last_name' => 'Doe',
+      'email' => 'doe@example.com',
+      'pass' => md5('doe-pass'),
+      'plain_pass' => 'doe-pass',
+      'groups' => 'staff',
+    ),
+    array(
+      'id' => mt_rand(101, 200),
+      'first_name' => 'Joe',
+      'last_name' => 'Roe',
+      'email' => 'roe@example.com',
+      'pass' => md5('roe-pass'),
+      'plain_pass' => 'roe-pass',
+      'groups' => 'staff;management',
+    ),
+    array(
+      'id' => mt_rand(201, 300),
+      'first_name' => 'Joe',
+      'last_name' => 'Bloggs ',
+      'email' => 'bloggs@example.com',
+      'pass' => md5('bloggs-pass'),
+      'plain_pass' => 'bloggs-pass',
+      'groups' => 'shareholder;management',
+    ),
+  );
+
+  $query = db_insert('migrate_example_people')
+    ->fields(array('id', 'first_name', 'last_name', 'email', 'pass', 'plain_pass', 'groups'));
+  foreach ($people as $record) {
+    $query->values($record);
+  }
+  $query->execute();
+
+  $content = array(
+    array(
+      'article_id' => mt_rand(0, 100),
+      'subject' => 'Foo',
+      'text' => 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur id metus ut est mollis congue ornare et enim. Mauris vel accumsan sem. In gravida eget libero et sagittis.',
+      'author' => $people[0]['id'],
+      'date' => '2004-12-12 13:45:06',
+    ),
+    array(
+      'article_id' => mt_rand(101, 200),
+      'subject' => 'Bar',
+      'text' => 'Morbi pellentesque sapien molestie eros aliquam porttitor. Nullam est dui, dapibus vulputate posuere sed, euismod sit amet libero. Nunc in lacus a enim malesuada luctus.',
+      'author' => $people[1]['id'],
+      'date' => '2008-02-23 03:56:32',
+    ),
+    array(
+      'article_id' => mt_rand(201, 300),
+      'subject' => 'Foo bar',
+      'text' => 'Mauris mollis auctor augue, sed pulvinar nunc sodales in. Integer est tortor, bibendum vel sodales ut, commodo a ligula. Morbi porta, lorem a laoreet facilisis, diam justo ullamcorper dui, eu commodo nunc lorem tempus purus.',
+      'author' => $people[2]['id'],
+      'date' => '2012-11-02 10:45:01',
+    ),
+    array(
+      'article_id' => mt_rand(301, 400),
+      'subject' => 'Qux baz',
+      'text' => 'Donec aliquam sed purus id posuere. Sed tincidunt ante vel semper malesuada. Sed imperdiet leo egestas eleifend egestas. Curabitur tristique sapien adipiscing nisi sodales pulvinar. Aenean commodo luctus lectus non pretium.',
+      'author' => $people[0]['id'],
+      'date' => '2001-01-02 08:19:13',
+    ),
+  );
+
+  $query = db_insert('migrate_example_content')
+    ->fields(array('article_id', 'subject', 'text', 'author', 'date'));
+  foreach ($content as $record) {
+    $query->values($record);
+  }
+  $query->execute();
+
+}
+
+/**
+ * Implements hook_schema().
+ *
+ * Defines the database tables used by this module.
+ *
+ * @see hook_schema()
+ * @ingroup migrate_example
+ */
+function migrate_example_schema() {
+  $schema['migrate_example_people'] = array(
+    'description' => 'Stores example person entries for demonstration purposes.',
+    'fields' => array(
+      'id' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => 'Primary Key: Unique person ID.',
+      ),
+      'first_name' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'First name of the person.',
+      ),
+      'last_name' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Last name of the person.',
+      ),
+      'email' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'E-mail of the person.',
+      ),
+      'pass' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The MD5 hashed password of the person.',
+      ),
+      'plain_pass' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The plain password of the person. Just for testing purposes.',
+      ),
+      'groups' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'The list of user groups separated by semicolon.',
+      ),
+    ),
+    'primary key' => array('id'),
+  );
+
+  $schema['migrate_example_content'] = array(
+    'description' => 'Stores example content entries for demonstration purposes.',
+    'fields' => array(
+      'article_id' => array(
+        'type' => 'int',
+        'not null' => TRUE,
+        'description' => 'Primary Key: Unique content ID.',
+      ),
+      'subject' => array(
+        'type' => 'varchar',
+        'length' => 255,
+        'not null' => TRUE,
+        'default' => '',
+        'description' => 'Article subject.',
+      ),
+      'text' => array(
+        'type' => 'text',
+        'size' => 'big',
+        'not null' => FALSE,
+        'description' => 'Article text body.',
+      ),
+      'author' => array(
+        'type' => 'int',
+        'not null' => FALSE,
+        'description' => 'Article author.',
+      ),
+      'date' => array(
+        'type' => 'varchar',
+        'length' => 20,
+        'not null' => TRUE,
+        'description' => "The article creation date in format 'Y-m-d H:i:s'.",
+      ),
+    ),
+    'primary key' => array('article_id'),
+  );
+
+  return $schema;
+}
diff --git a/migrate_example/migrate_example.module b/migrate_example/migrate_example.module
new file mode 100644
index 0000000..885a1d1
--- /dev/null
+++ b/migrate_example/migrate_example.module
@@ -0,0 +1,48 @@
+<?php
+
+/**
+ * @file
+ * migrate_example module file.
+ */
+
+/**
+ * @defgroup migrate_example Example: Migrate
+ * @ingroup examples
+ * @{
+ * A basic example of defining a migration through configuration YAML files and
+ * custom source and process plugins.
+ *
+ * In this example we are performing three migrations: roles, users, content.
+ * The source of the migration is proprietary hypothetical CMS into Drupal 8,
+ * using the migrate API provided by the core Migrate module. The biggest part
+ * of the migration is defined in the migration YAML files, placed under
+ * 'config/' directory.
+ *
+ * However, migrate config files are not enough because we still need to implement some migration plugins. The module implements a source plugin for each migration and a process plugin for transforming the value to be imported. Sandbox data is defined in migrate_example.install file and it's used in tests provided by the module.
+ *
+ * Migrate examples provided by the module:
+ *
+ * - migrate_example_user_role: Migrates the source groups into Drupal roles. The source doesn't keep a separate table for groups, we are processing them from the people table.
+ * - migrate_example_people: Migrates the list of users into Drupal. This migration request a simple custom process plugin 'explode' that expands the list of user groups from a semicolon delimited string to an array.
+ * - migrate_example_content: Migrates the source articles into destination node entities of type 'page'. Note that the article author is migrated using the map of the previous users migration.
+ *
+ * How to test?
+ *
+ * There are three simpletest tests provided for each migration or you can simply enable the module and run manually the migrations.
+ *
+ * @todo The 'body' field to be migrated in https://drupal.org/node/2164451.
+ * @}
+ */
+
+/**
+ * Implements hook_menu().
+ *
+ * Provides a default page to explain what this module does.
+ */
+function migrate_example_menu() {
+  $items['examples/migrate_example'] = array(
+    'title' => 'Migrate Example',
+    'route_name' => 'migrate_example.description',
+  );
+  return $items;
+}
diff --git a/migrate_example/migrate_example.routing.yml b/migrate_example/migrate_example.routing.yml
new file mode 100644
index 0000000..7949e46
--- /dev/null
+++ b/migrate_example/migrate_example.routing.yml
@@ -0,0 +1,6 @@
+migrate_example.description:
+  path: '/examples/migrate_example'
+  defaults:
+    _content: '\Drupal\migrate_example\Controller\MigrateExampleController::description'
+  requirements:
+    _access: 'TRUE'
