Last updated March 24, 2014. Created by mikeryan on April 20, 2011.
Edited by Dane Powell, blazey, cthos, benjifisher. Log in to edit this page.

Source fields are mapped to destination fields by making calls to $this->addFieldMapping() in your Migration class constructor. Field mappings are represented as instances of the MigrateFieldMapping class, which implements a fluent interface akin to that of the Drupal 7 database API. This enables optional behavior to be attached to mappings, without using a long list of optional arguments on addFieldMapping().

<?php
   
// Each method returns a FieldMapping object
   
$this->addFieldMapping('sticky')
         ->
description(t('Should we default this to 0 or 1?'))
         ->
issueGroup(t('Client questions'))
         ->
issueNumber(765736)
         ->
issuePriority(MigrateFieldMapping::ISSUE_PRIORITY_LOW);
?>

The following methods can be chained:

  • ->description($description): provide a description for the mapping. This will be displayed in the Migrate UI.
  • ->defaultValue($value): provide a default value for this field.
  • ->separator($delimiter): the source string will be exploded to an array using the specified delimiter.
  • ->callbacks($callback1, $callback2, ...): adds functions to be executed in order to prepare the data for that specific field.
  • ->sourceMigration(array($migrationMachineName, ...)): If specified, the source value in the mapping will be looked up in the map tables for any listed migrations and if found, translated to the destination ID from the map table.
  • ->dedupe($table, $column): Search for duplicate value in $table and $column. When a duplicate value is found a counter will be appended.
  • ->issueGroup($group): Each issue group will generate a tab on the detail page in the UI, for grouping the mappings.
  • ->issueNumber($group): If present, used to link to an issue in an external ticketing system.
  • ->issuePriority($group): Indicates the priority of an issue (with highlighting for non-OK values).

Simple mapping

In its simplest form, a field mapping defines a straight copy of source data to the destination Drupal object:

<?php
$this
->addFieldMapping('title', 'source_subject');
?>

In a node migration, this indicates that when each source row is processed, the contents of its source_subject field will be copied to the node title.

It's not uncommon for fields to have the same name on both the source and destination sides. For example, when migrating nodes from a system with title and body fields, you might expect to write:

<?php
$this
->addFieldMapping('title', 'title');
$this->addFieldMapping('body', 'body');
?>

But, you can take a shortcut:

<?php
$this
->addSimpleMappings(array('title', 'body'));
?>

Similarly, when you have several destination or source fields which are not being migrated, rather than writing out an addFieldMapping() call (with issueGroup() attached) for each one, you can do it in one call:

<?php
$this
->addUnmigratedDestinations(array('status', 'uid'));
$this->addUnmigratedSources(array('old_field', 'older_field', 'oldest_field'), t('Do Not Migrate'));
?>

The issue group for these mappings defaults to DNM, but as you see in the source case above it can be overridden using the optional second parameter.

Provide Default values

A default value can be provided:

<?php
$this
->addFieldMapping('field_age_range', 'source_age_range')
     ->
defaultValue(t('Unknown age range'));
?>

This indicates that for every source row with a NULL or empty source_age_range value, the resulting field_age_range should be set to 'Unknown age range'.

By omitting the source field, you can hard-code a value to be applied to every migrated row - in this case, we ensure that every migrated user has the "authenticated user" role:

<?php
$this
->addFieldMapping('roles')
     ->
defaultValue(DRUPAL_AUTHENTICATED_RID);
?>

Convert source strings to arrays

Multiple values for a field can be migrated directly:

<?php
$this
->addFieldMapping('field_tags', 'tags')
     ->
separator(',');
?>

If the source field tags contains a comma-separated list of tags (e.g., "music,tv,radio"), field_tags will be assigned an array of the individual tags (array('music', 'tv', 'radio')).

Subfields

When a destination field has options which can be set to control its behavior, or requires multiple pieces of data, these can be set using subfields. For example, to set a body and its summary:

<?php
    $this
->addFieldMapping('body', 'body');
   
$this->addFieldMapping('body:summary', 'excerpt');
?>

The subfields are exposed on the migration detail pages just like regular fields, and all the power of field mappings can be applied to them.

For more complex fields (such as Name or Address), it might not be obvious what should be mapped to the "primary" field component. By default, the first subfield is used as the primary field, but this can be overridden by individual modules. Consult the specific module / field documentation in those cases.

Callbacks

Sometimes you need to perform some transformation on a single field. You could do this in prepareRow(), but the simplest thing to do is add a callback to the field. This isolates the manipulation from any more complex work going on in prepareRow(), lets you apply standard PHP functions without adding anything outside of the field mapping, and also lets you share a function among multiple fields.

<?php
 
// Incoming text is ISO-8859
 
$this->addFieldMapping('field_text_stuff', 'source_text')
       ->
callbacks('utf8_encode');
 
$this->addFieldMapping('field_computed_value', 'field_base_value')
       ->
callbacks(array($this, 'computeValue'));
...
  protected function
computeValue($value) {
   
$value = $value*10 + 28.3;
    return
$value;
  }
?>

Note that the callbacks are applied to the source $row field after prepareRow() is called.

sourceMigration

A key feature of Migrate is the ability to maintain relationships among imported objects, even when their primary keys have changed during import. For example, a user account may have had the unique ID 123 on the legacy system, but the Drupal user account created from that legacy account data gets the uid 456. The legacy system may thus have had an author ID of 123 on that account's content, which we want to store as a uid of 456 on the Drupal nodes. This is accomplished by using sourceMigration.

<?php
$this
->addFieldMapping('uid', 'author_id')
     ->
sourceMigration('Users');
?>

What this is saying is that the author_id value (e.g., 123) should be used to query the Users migration's map table to retrieve the corresponding Drupal ID (456), which will then be assigned to the node uid field.

Multiple sourceMigrations for a single value

Migrate also supports the possibility that the Drupal Id for a given field may have been generated by any number of separate migrations. For example, the Author of the node in our example above could have been generated by our 'Users' migration, but it also might have been generated by an 'Authors' migration which also creates users.

In this case, you can simply pass an array to sourceMigration() like so:

<?php
$this
->addFieldMapping('uid', 'author_id')
     ->
sourceMigration(array('Users', 'Authors'));
?>

Migrate will loop through the listed source migrations in the order given, and will stop at the first destination id that it finds.


Deduping

In some cases, Drupal may require a given field be unique for every object, but the source system may not have had the same requirement. You can automatically generate deduped values in these instances. In this example, while Drupal requires that usernames be unique, the source system we're coming from did not:

<?php
$this
->addFieldMapping('name', 'username')
     ->
dedupe('users', 'name');
?>

The dedupe() arguments are the Drupal table and column to check for duplicates. When a migrate-import is run, if the first record has a username of 'mike', a Drupal user with name 'mike' is created. If later another record has a username of 'mike', the dedupe handling will query users.name and discover it already exists, so it will modify the name value to 'mike_1'. It will also save an informational message to the migration's message table, so you know when values have been modified (for example, you might use the message table to send emails to users letting them know their usernames have been changed).

Documenting mappings

The above methods cover all the methods that affect the actual operation of data import. There are a number of other methods and techniques, however, which aid in documenting your migration. If you have enabled the migrate_ui module, clicking on a migration name on the Migrate dashboard brings you to a page documenting the migration - the source, the destination, and especially the mappings. Regular review of this page can help ensure nothing gets missed.

Every field mapping has an Issue Group associated with it - on the migration information page, each group is rendered on a vertical tab named "Mapping: <group>". If you don't provide a group explicitly using issueGroup() (see below), it defaults to "Done".

Now, it's a rare migration where you use every available source field, and populate every available Drupal field directly from a source field. Usually some source data will simply be ignored, while some Drupal fields will be allowed to fallback to their defaults. You could simply not map these at all, but it is a good practice to make these decisions explicit - to clearly indicate that someone has consciously decided these fields will not be mapped. A helpful feature of migrate_ui is that it highlights on the Destination and Source tabs any fields that have no mappings - by explicitly marking fields that are not to be migrated, you can distinguish the fields you know you don't want to migrate from those you haven't dealt with yet, or new fields that have appeared and need to be dealt with. So, a good convention is to create NULL mappings within a group named "Do Not Migrate" (or "DNM", if you're a lazy typist).

<?php
// Unmapped destination fields
$this->addFieldMapping('status')
     ->
issueGroup(t('DNM'));
$this->addFieldMapping('uid')
     ->
issueGroup(t('DNM'));
// Unmapped source fields
$this->addFieldMapping(NULL, 'obsolete_data')
     ->
issueGroup(t('DNM'));
?>

Now, developing a complex migration process may take weeks or months - you are not going to know exactly what is being migrated and how when you initially write your migration class. Consider this example:

<?php
$this
->issuePattern = 'http://drupal.org/node/:id:';
...
$this->addFieldMapping('field_ingredients')
     ->
issueGroup(t('Client issues'))
     ->
description(t('Where will the ingredient data come from?'))
     ->
issuePriority(MigrateFieldMapping::ISSUE_PRIORITY_MEDIUM)
     ->
issueNumber(770064);
?>

The Migrate module was created in the context of consultants developing migrations for clients, so our usual convention was to have a "Client issues" group for anything the client needs to address (usually providing information on how or whether they want a field to be migrated) and an "Implementor issues" group for mappings which are fully specified but haven't been completely implemented yet. Note that descriptions (displayed on the migration detail page) can be added to any mapping, and are recommended for all but the most trivial cases. The issuePriority defaults to MigrateFieldMapping::ISSUE_PRIORITY_OK - if any other value, it will be highlighted on the detail page. The values for priorities are:

  • ISSUE_PRIORITY_OK
  • ISSUE_PRIORITY_LOW
  • ISSUE_PRIORITY_MEDIUM
  • ISSUE_PRIORITY_BLOCKER

Finally, note the issuePattern setting on the migration class and the issueNumber on the field mapping. This can be used to link to a ticket or issue in an issue-tracking system such as Unfuddle or Jira - the issueNumber is plugged into the :id: placeholder in the issuePattern to generate the link.

Removing field mappings

Usually, you will be implementing multiple Migration classes which share some common attributes (such as $this->issuePattern). To share such commonality, you would derive an intermediate abstract class directly from Migration, then derive each of your concrete classes from the intermediate class. In some cases, you may even be able to share a number of field mappings among multiple migrations by including them in the intermediate class. But, what if you have a mapping that applies to 9 out of your 10 migrations (e.g., you have a taxonomy reference field_tags on all but one content type)? What you can do is add the field mapping in the common constructor, then remove it in the one exception:

<?php
abstract class CommonMigration extends Migration {
  public function
__construct() {
   
parent::__construct();
...
   
$this->addFieldMapping('field_tags', 'tags');
...
  }
}
class
PageMigration extends CommonMigration {
  public function
__construct() {
   
parent::__construct();
...
 
$this->removeFieldMapping('field_tags');
...
  }
}
?>

Looking for support? Visit the Drupal.org forums, or join #drupal-support in IRC.

Comments

I have a problem with migrating over a field.
I want it to be a boolesk, but i have male, female and the ones that haven't choosen.
I want it to not do anything if the gender isn't set, but all it does is trying to set it to an empty string and the 'row' doesn't get imported.
Anyone got an idea ?
Here is my code:

$this->addFieldMapping('field_gender', 'gender')
->defaultValue(NULL) // empty = unknown
->callbacks(array($this, 'fixGender'))
->description(t('Gender'));

public function fixGender($gender) {
$gender = trim(strtolower($gender));
if($gender == "female" ) return "0";
if($gender == "male" ) return "1";
return false;
}

i needed a prepareRow.

public function prepareRow($row) {
$gender = trim(strtolower($row->gender));
if(empty($gender)) unset($row->gender);

}