Migrate Field Collections

Last updated on
30 April 2025

This page will describe the process of migrating field collections.

Field collections are separate entities, which are linked to host entities. That is why we have to create separate migration classes for every field collection field.

Your field collection migration should be run after the host entity migration. For example, if the collection is attached to nodes via a field named field_attached_data, and if the Article nodes are being imported by ArticleMigration, your collection migration class constructor will contain the code described below.

  1. Specify dependencies
    $this->dependencies = array('Article');
    // 'Article' is the name given to the migration in your hook_migrate_api implementation.
    

    Because of this dependency, our field collection class ArticleAlbumMigration will not be able to be imported until ArticleMigration is fully imported.

  2. Specify destination
    $this->destination = new MigrateDestinationFieldCollection(
      'field_attached_data',
      array('host_entity_type' => 'node')
    );
    

    NOTE: if the host is a term, the correct host_entity_type would be 'taxonomy_term'.

  3. Map host entity ID to source entity ID
    $this->addFieldMapping('host_entity_id', 'source_article_id')
      ->sourceMigration('Article');
    

In general Field Collection migration works like any entity migration.

Working example

In this example we will set up a migration for an Article content type with an attached field collection field.

The Article content type has title and description fields only.

Let's assume we would like to add a list of album tracks to our Article. Let's create an Album field and set it type to Field Collection. In this example we will use a field with an unlimited number of values. Now we'll add fields to the Album field collection. Two fields will be enough: Title (Text) and Length (Integer).

Now we have fully configured the content type. Let's create the migration module. We will migrate data from a CSV source.

Module files

fc_migration
├── data
│   ├── albums.csv
│   └── articles.csv
├── fc_migration.info
├── fc_migration.migrate.inc
├── fc_migration.module
└── migrations
    ├── albums.inc
    └── articles.inc

Contents of articles.csv

id,title,field_description
1,The Fame,The Fame is the debut studio album by Lady Gaga.
2,Born This Way,Born This Way is the second studio album by Lady Gaga.

Contents of albums.csv

id,article,field_title,field_length
1,1,Just Dance,240
2,1,LoveGame,215
3,2,Marry the Night,265
4,2,Born This Way,260

Let's define hook_migrate_api(). The best place to do it is in fc_migration.migrate.inc.

/**
 * Implements hook_migrate_api().
 */
function fc_migration_migrate_api() {
  $api = array(
    'api' => 2,
    'migrations' => array(
      'Article' => array('class_name' => 'ArticleMigration'),
      'ArticleAlbum' => array('class_name' => 'ArticleAlbumMigration'),
    ),
  );
  return $api;
}

Don't forget to include your classes in the module's info file:

files[] = migrations/articles.inc
files[] = migrations/albums.inc

This is the Article migration class

class ArticleMigration extends Migration {
  public function __construct() {
    parent::__construct();

    $this->description = t('Articles migration from CSV source.');

    $columns = array(
      array('id', 'Article ID'),
      array('title', 'Title'),
      array('field_description', 'Description'),
    );

    $this->source = new MigrateSourceCSV(
      drupal_get_path('module', 'fc_migration') . '/data/articles.csv',
      $columns,
      array('header_rows' => 1)
    );

    $this->destination = new MigrateDestinationNode('article');

    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'id' => array(
          'type' => 'int',
          'not null' => true,
        ),
      ),
      MigrateDestinationNode::getKeySchema()
    );

    $this->addFieldMapping('uid', NULL)->defaultValue(1);
    $this->addSimpleMappings(array(
      'title',
      'field_description',
    ));
  }
}

This is the Albums migration class


class ArticleAlbumMigration extends Migration {
  public function __construct() {
    parent::__construct();

    $this->description = t('Album field migration from CSV source.');
    $this->dependencies = array('Article');

    $columns = array(
      array('id', 'Source ID'),
      array('article', 'Article ID'),
      array('field_title', 'Title'),
      array('field_length', 'Length'),
    );

    $this->source = new MigrateSourceCSV(
      drupal_get_path('module', 'fc_migration') . '/data/albums.csv',
      $columns,
      array('header_rows' => 1)
    );

    $this->destination = new MigrateDestinationFieldCollection(
      'field_album',
      array('host_entity_type' => 'node')
    );

    $this->map = new MigrateSQLMap($this->machineName,
      array(
        'id' => array(
          'type' => 'int',
          'not null' => true,
        ),
      ),
      MigrateDestinationFieldCollection::getKeySchema()
    );

    $this->addFieldMapping('host_entity_id', 'article')->sourceMigration('Article');
    $this->addSimpleMappings(array(
      'field_title',
      'field_length',
    ));
  }
}

Now our module should be ready to perform migrations.

Note: There are plenty of suggestions and other examples in this issue.

Help improve this page

Page status: Not set

You can: