For non-trivial migrations, you will find the need to go beyond simply mapping fields through the admin interface - the translation of a field may require some runtime logic, or you may be migrating into a destination not already supported by the migrate module. To accomplish these things, you will need to implement one or more of the following hooks.

hook_migrate_init()

This hook is called by the migrate module the first time it prepares to call any hook. The purpose is to allow modules implementing the hooks to do any one-time initialization. In particular, by implementing all of your hooks and migration support in a separate .inc file, and including it in hook_migrate_init() in your .module file, you can minimize the code size impact of your migration support except when it is actually needed.

function example_migrate_init() {
  $path = drupal_get_path('module', 'example') . '/example_migrate.inc';
  include_once($path);
}

hook_migrate_types()

Implement this hook to set up custom destinations. It should return an array in which each key is to be used to form other hook names, and each value is the user-friendly name of the destination. If your module is going to support subtypes (as the node module has various content types), then the key will the the destination name followed by a slash, followed by the subtype (e.g., 'node/article').

function example_migrate_types() {
  $types = array('sub' => t('Subscription'));
  return $types;
}

hook_migrate_fields_{destination}($desttype = NULL)

Having told the migrate module what destinations you are supporting, next you need to tell it what fields each destination supports (i.e. the right side of the mappings section of the content set editor). This hook returns an array in which each key is the index of the field (usually the database column name from the table where the value will ultimately be stored) and the value is a user-friendly name for the field. If the destination supports subtypes, the specific subtype is passed as an argument. Note that you can implement this hook for destinations you are not defining - for example, if your module adds fields to nodes, you can implement hook_migrate_fields_node() to expose those fields to migration.

The primary key of the destination should be indicated by enclosing its index in square brackets (see subid below).

function example_migrate_fields_sub($type) {
  return array(
    '[subid]' => t('Subscription: Unique subscription identifier'),
    'userid' => t('Subscription: User id'),
  );
}

hook_migrate_delete_{destination}($destid)

This hook is called on a clearing operation to delete a given destination object.

function example_migrate_delete_sub($subid) {
  db_query("DELETE FROM {example_subs} WHERE subid=%d", $subid);
}

hook_migrate_import_{destination}($tblinfo, $row)

When processing an import (or scan) operation, the migrate module will call this hook for each previously-unimported row of the sourceview. $tblinfo will contain metadata about the content set (most importantly the mapping info), and $row will contain the source data row. The import hook will create the corresponding destination object from this information, and return an array of message objects (typically empty if nothing unusual has happened).

function example_migrate_import_sub($tblinfo, $row) {
  $sub = new stdClass;
  // Initially populate the new object according to the mappings
  foreach ($tblinfo->fields as $destfield => $values) {
    if ($values['srcfield'] && $row->$values['srcfield']) {
      $sub->$destfield = $row->$values['srcfield'];
    }
    else {
      $sub->$destfield = $values['default_value'];
    }
  }

  // Give other modules a shot at manipulating the object
  $errors = migrate_destination_invoke_all('prepare_sub', $sub, $tblinfo, $row);

  $success = TRUE;
  foreach ($errors as $error) {
    if ($error['level'] != MIGRATE_MESSAGE_INFORMATIONAL) {
      $success = FALSE;
      break;
    }
  }
  if ($success) {
    $newid = example_sub_save($sub);
    // Call completion hooks, for any processing which needs to be done after node_save
    $errors = migrate_destination_invoke_all('complete_sub', $sub, $tblinfo, $row);

    $sourcekey = $tblinfo->sourcekey;
    migrate_add_mapping($tblinfo->mcsid, $row->$sourcekey, $newid);
  }
  return $errors;
}

hook_migrate_xlat_{destination}($destid)

Given the ID of a destination object, return the URI of the object.

function example_migrate_xlat_sub($subid) {
  return "example/sub/$subid";
}

hook_migrate_prepare_{destination}(&$node, $tblinfo, $row)

As you can see from the import hook sample above, a prepare hook is called after the destination object has been filled in using the specified mappings, but before it is actually created - this allows other modules to perform their own manipulations on the object.

function example_migrate_prepare_node(&$node, $tblinfo, $row) {
  $errors = array();
  $node->our_custom_time = time();
  return $errors;
}

hook_migrate_complete_{destination}

The complete hook is called after successfully saving the migrated object - this allows other modules to perform operations that require the object to already exist (and have an ID), such as save data related to that object.

function example_migrate_complete_node(&$node, $tblinfo, $row) {
  $errors = array();
  db_query("INSERT INTO {example_related} (nid, our_customer_time) VALUES(%d, %d)",
    $node->nid, $node->our_custom_time);
  return $errors;
}