On this page
How to create nodes with hierarchical terms from excel
The purpose of this cookbook is to show how to create node from a excel documents via csv files.
The example we use is a list of stores with name, adress and three column to convert to terms a state,capital ubication and a kind of store column.
Create migrate module
First thing we are going to do is make a tipically module named migratestores.
migratestores.info
name = "Migration of Stores"
description = "Migration module"
package = "Development"
core = 7.x
dependencies[] = migrate
version = "1.0"
files[] = stores.inc
As we can see, we need to having installed Migrate and Migrate UI modules. Download and install it.
https://www.drupal.org/project/migrate
Check the following structure:
| Number | State | Capital | Name | Kind of store | Telephone |
| 1 | Distrito federal | Distrito federal | La tienda grande | drugstores | 6546546546 |
| 2 | Puebla | Puebla | Las semitas | restaurant | 7897987988 |
| 3 | Estado de Mexico | Toluca | Libros en frio | bookstores | 6546546546 |
| 4 | Jalisco | Guadalajara | La principal | bookstores | 7897987988 |
| 5 | Nuevo leon | Monterrey | El cabrito | restaurant | 6546546546 |
| 6 | Veracruz | Xalapa | La cruz | drugstores | 7897987988 |
| 7 | Michoacan | Morelia | La cura | drugstores | 6546546546 |
| 8 | Chiapas | Tuxtla Gutierrez | La farmacia chiquita | drugstores | 7897987988 |
| 9 | Tabasco | Villahermosa | libros del horno | bookstores | 7897987988 |
| 10 | Oaxaca | Oaxaca | La mescaleria | bookstores | 7897987988 |
As you can see, we have repeated data. If we see this as a relational database, we can easily assume that we need a table for Cities, another for States, other for Kind of Store and finally a table of content which would have names and phones.
Lets do some work at Drupal Content Type section.
First we need to define two Taxonomies. (Location and Kind).
We’ll add vocabularies, and change their machine names for further purposes.
Once we’ve created taxonomies, we are gonna create the Content Type.
Add Location field
Choose the vocabulary we created previously
Add Kind field
With the terms
Add phone field

Now lets do some code.
Create an empty file and name it migratestores.module
Now we are going to write in migratestores.migrate.inc This file we’ll be recognized by Migrate UI module, and will show our migrations on corresponding section.
<?php
function migratestores_migrate_api() {
$api = array(
'api' => 2,
'groups' => array(
'stores' => array(
'title' => t('Stores imports'),
)
),
'migrations' => array(
),
);
return $api;
}
?>We need to write a function with module’s prefix followed by migrate_api() and this need to return an api array. The array have an api version, the group for our migrations. Lately we’ll fill the migrations array.
stores.inc
<?php
abstract class BasicStoresMigration extends Migration {
// A Migration constructor takes an array of arguments as its first parameter.
// The arguments must be passed through to the parent constructor.
public function __construct($arguments) {
parent::__construct($arguments);
// With migrate_ui enabled, migration pages will indicate people involved in
// the particular migration, with their role and contact info. We default the
// list in the shared class; it can be overridden for specific migrations.
$this->team = array(
new MigrateTeamMember('Daniel García', 'daniel@dragonflylabs.com.mx',
t('Developer')),
);
// Individual mappings in a migration can be linked to a ticket or issue
// in an external tracking system. Define the URL pattern here in the shared
// class with ':id:' representing the position of the issue number, then add
// ->issueNumber(1234) to a mapping.
$this->issuePattern = 'http://drupal.org/node/:id:';
}
}
?>Explanation is not necesary.
In the same file:
<?php class KindMigration extends BasicStoresMigration{
public function __construct($arguments){
parent::__construct($arguments);
$module_path = DRUPAL_ROOT . '/' . drupal_get_path('module', 'migratestores');
//File with our data
$csv_path = $module_path . '/kinds.csv';
// Specify CSV columns
$columns = array(
0 => array('id_csv_kind', 'Id'),
1 => array('kind_name','Kind')
);
$this->description = t('Migrate Kinds');
// Migration configuration
$this->source = new MigrateSourceCSV($csv_path, $columns, array('delimiter' => ',', 'header_rows' => 1));
//Taxonomy destiny
$this->destination = new MigrateDestinationTerm('taxonomy_kind');
$this->map = new MigrateSQLMap($this->machineName,
array(
'id_csv_kind' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'alias' => 'import'
)
),
MigrateDestinationNode::getKeySchema()
);
//Here we put the machine name that we put later in Drupal and match with column name of CSV
$this->addFieldMapping('name', 'kind_name');
}
}
class LocationMigration extends BasicStoresMigration{
public function __construct($arguments){
parent::__construct($arguments);
$module_path = DRUPAL_ROOT . '/' . drupal_get_path('module', 'migratestores');
$csv_path = $module_path . '/locations.csv';
$columns = array(
0 => array('id_csv_location', 'Id'),
1 => array('location_name','Name'),
2 => array('parent', 'Parent')
);
$this->description = t('Migrate locations');
$this->source = new MigrateSourceCSV($csv_path, $columns, array('delimiter' => ',', 'header_rows' => 1));
$this->destination = new MigrateDestinationTerm('taxonomy_location');
$this->map = new MigrateSQLMap($this->machineName,
array(
'id_csv_location' => array(
'type' => 'varchar',
'unsigned' => TRUE,
'not null' => TRUE,
'alias' => 'import'
)
),
MigrateDestinationNode::getKeySchema(),
'loslos'
);
$this->addFieldMapping('name', 'location_name');
$this->addFieldMapping('parent_name', 'parent');
}
}
class StoresMigration extends BasicStoresMigration{
public function __construct($arguments){
parent::__construct($arguments);
$module_path = DRUPAL_ROOT . '/' . drupal_get_path('module', 'migratestores');
$csv_path = $module_path . '/stores.csv';
// We can safely delete the state column, just the City columns, in this case renamed as Location
$columns = array(
0 => array('id_csv_stores', 'Id'),
1 => array('location_store','Location'),
2 => array('name_store', 'Name'),
3 => array('kind_store', 'Kind'),
4 => array('phone_store','Telephone')
);
$this->description = t('Stores Migration');
$this->source = new MigrateSourceCSV($csv_path, $columns, array('delimiter' => ',', 'header_rows' => 1));
// Our destination Content Type
$this->destination = new MigrateDestinationNode('content_store');
$this->map = new MigrateSQLMap($this->machineName,
array(
'id_csv_stores' => array(
'type' => 'int',
'unsigned' => TRUE,
'not null' => TRUE,
'alias' => 'import'
)
),
MigrateDestinationNode::getKeySchema()
);
// Now, we reference the list of terms
$this->addFieldMapping('field_location', 'location_store')->arguments(array('source_type' => 'term'));
$this->addFieldMapping('title', 'name_store')->arguments(array('source_type' => 'term'));
$this->addFieldMapping('field_kind', 'kind_store')->arguments(array('source_type' => 'term'));
$this->addFieldMapping('field_phone', 'phone_store');
}
}
?>
We’ve created our three migrations necessary.
Now, we are going to organize our CSV’s content like follow:
cities.txt
kinds.txt
stores.txt
The last step is to registering our Migrates into migratestores.migrate.inc
<?php
'migrations' => array(
'StoresKind' => array(
'class_name' => 'StoresKindMigration',
'group_name' => 'stores'
),
'StoresLocation' => array(
'class_name' => 'StoresLocationMigration',
'group_name' => 'stores'
),
'Stores' => array(
'class_name' => 'StoresMigration',
'group_name' => 'stores'
)
),
?>Now we’ll open the Module section and enable our module created in Development section.
Once enabled, go to Migrate tab located at Content superior menu.
NOTE: Maybe you’ll see a Field mapping error, just ignore it.
We can see our group created

After that, just check the migration and apply Migrate.
Help improve this page
You can:
- Log in, click Edit, and edit this page
- Log in, click Discuss, update the Page status value, and suggest an improvement
- Log in and create a Documentation issue with your suggestion