Skip to content

How-to guideFor module developersDrupal 11

Migrations

The Migrate API imports data into Drupal entities from almost any source, a CSV, a database, a JSON API, an old Drupal site, while tracking what it imported so you can re-run or roll it back. A migration is a YAML definition with three parts: a source, a process pipeline, and a destination.

Import external data into content entities repeatably. The worked example imports two articles from data embedded in the migration; the same shape scales to real sources by swapping the source plugin.

Migrate is not the only way to create entities in bulk, and for a one-time import of a manageable set it is overkill: a plain entity-creation loop (Create, load, and query entities) wrapped in a batch or queue for volume (Background processing) is far less machinery. Migrate earns its YAML when the import must be repeatable: it tracks every row it creates, so you can re-run against updated source data, resume after a failure, or roll the whole thing back.

  • A custom module, per Scaffold a module.
  • The core Migrate module (ddev drush pm:install migrate). Drush itself provides the migrate:status, migrate:import, and migrate:rollback commands used below.
  • Entities, bundles, and fields for the destination side.

Migrations live in your module’s migrations/ directory as ID.yml. The embedded_data source carries its rows inline, ideal for learning and for fixed lookup data; the process keys map source fields to destination fields; the entity:node destination saves each row as a node.

web/modules/custom/howto_migrate/migrations/howto_articles.yml
# A migration has three parts: a source that yields rows, a process
# pipeline that maps source fields to destination fields, and a
# destination that saves each row as an entity.
id: howto_articles
label: 'Import articles from embedded data'
migration_tags:
- howto
source:
# embedded_data carries its rows inline; swap for csv, url, or a custom
# source plugin against a real system.
plugin: embedded_data
data_rows:
- id: 1
headline: 'Welcome to the site'
summary: 'Our first imported article.'
- id: 2
headline: 'Second post'
summary: 'Another row from the source.'
ids:
id:
type: integer
process:
# destination_field: source_field, or a pipeline of process plugins.
title: headline
'body/value': summary
'body/format':
plugin: default_value
default_value: basic_html
destination:
plugin: 'entity:node'
default_bundle: article

How the three parts connect:

  • Source yields rows and declares which fields form each row’s unique ids.
  • Process maps each destination field. title: headline is a direct copy; a nested key like body/value targets a field property; a process plugin (default_value, here) transforms or supplies a value. Many plugins can chain into a pipeline.
  • Destination names the entity type, with default_bundle for the bundle.

Rebuild caches so Drupal discovers the file: ddev drush cr.

Terminal window
ddev drush migrate:status howto_articles # see counts and state
ddev drush migrate:import howto_articles # run it
ddev drush migrate:rollback howto_articles # undo it

The Migrate API records a map from source IDs to created entities. That map prevents duplicates: re-running migrate:import skips rows already imported, and ddev drush migrate:import howto_articles --update re-applies the current source data to them. A rollback deletes exactly what the migration created.

After importing, the nodes exist:

Terminal window
ddev drush php:eval "echo count(\Drupal::entityTypeManager()->getStorage('node')->loadByProperties(['type' => 'article']));"

prints 2. The example module’s kernel test runs the migration and asserts both articles are created.

Swap embedded_data for csv (contrib Migrate Source CSV), url (for JSON and XML feeds, from contrib Migrate Plus), or a custom source plugin in src/Plugin/migrate/source/ when you query a system Migrate does not already speak.

The Migrate Drupal module provides source plugins and upgrade paths for Drupal 6 and 7 sites, and the upgrade UI and the contrib Migrate Upgrade module’s drush migrate:upgrade build on the same API shown here. Migrate Drupal is deprecated in Drupal 11.4; its lifecycle entry on drupal.org explains where it goes next.

Was this page helpful?

Drupal is a registered trademark of Dries Buytaert.