This guide covers Drupal migration using the Migrate module. Picture this: you're migrating an e-commerce store with 80,000 products, order history, and user profiles from an old Drupal 7 to Drupal 10. Categories are restructured, images stored in non-standard paths, and the comments table holds 300,000 records with nested replies up to 10 levels deep. Doing this manually would take months, and errors would be inevitable. We use Drupal's built-in Drupal ETL framework — the Migrate module — which extracts, transforms, and loads data. Over five years, we've completed 50+ migrations, ensuring data integrity (99.9% rate) and full compliance with the new structure. With proper configuration, Migrate processes 10,000 records in 15–20 minutes, achieving over 10,000 records per hour. Our typical migration costs range from $500 to $3000 depending on complexity. Reach out to us for a project audit and an optimal migration plan.
Architecture of Migrate
The module consists of three logical components configured via YAML files:
- Source — data source: CSV, SQL, JSON, old Drupal 7, WordPress, any API.
- Process — transformation: field mapping, format conversion, enrichment via plugins.
- Destination — where to write: Node, Term, User, File, Config.
Each migration is defined in a config/install/migrate_plus.migration.*.yml file. We use Migrate Plus, Migrate Tools, and additional source plugins. All migrations inherit from the base Migration class.
| Criteria | Migrate | Manual Import |
|---|---|---|
| Speed | Hours for 10,000 records | Days or weeks |
| Transformations | Built-in plugins | Manual scripts |
| Error handling | Logging, continue-on-failure | No control |
| Reusability | YAML configs | Start from scratch each time |
Migrate is typically 10x faster than manual transfer when comparing development and execution time.
How to Set Up a Migration in Drupal?
The process involves six steps:
- Analyze source data: structure, types, relationships, volume. For example, determine that categories are stored in a separate table and images are external URLs.
- Design migrations: field mapping, select source plugins, define dependencies (e.g., categories first, then articles).
- Implementation: write YAML, custom plugins, test on a sample of 10–20 records.
- Testing: verify integrity, regression, fix errors — run on a full copy.
- Execution: phased import with monitoring, rollback if needed.
- Support: adjustments for new sources, updates after Drupal releases.
What Data Sources Are Supported?
According to the Drupal Migrate documentation on drupal.org, the module natively supports CSV, JSON, XML, SQL databases via PDO, and older Drupal versions (6, 7). A ready-made source plugin exists for WordPress, and custom plugins are used for arbitrary APIs. Our methods are compatible with Drupal 8 migration as well. The table below shows popular options:
| Source | Plugin | Example Use Case |
|---|---|---|
| CSV | csv |
Import from Excel export |
| JSON | json |
REST API of a third-party service |
| WordPress | wordpress |
WordPress migration: Blog migration from WP |
| Drupal 7 | d7_node |
Upgrade to Drupal 10 |
| Custom API | custom source |
Integration with CRM |
Example: Migration from CSV
For CSV import Drupal, use the csv plugin as shown below.
View CSV migration configuration
id: articles_from_csv
label: 'Articles from CSV'
migration_group: content_import
source:
plugin: csv
path: 'public://import/articles.csv'
ids:
- external_id
header_row_count: 1
column_names:
- external_id
- title
- body
- category
- publish_date
- image_url
process:
title: title
'body/value': body
'body/format':
plugin: default_value
default_value: full_html
created:
plugin: format_date
source: publish_date
from_format: 'd.m.Y'
to_format: 'U'
status:
plugin: default_value
default_value: 1
field_category:
plugin: migration_lookup
migration: categories_from_csv
source: category
field_image:
plugin: download
source:
- image_url
- '@filename'
destination:
plugin: 'public://images'
rename: true
destination:
plugin: 'entity:node'
default_bundle: article
migration_dependencies:
required:
- categories_from_csv
Developing a Drupal custom source plugin is straightforward. Example custom source plugin for an API:
<?php
// src/Plugin/migrate/source/ExternalApiSource.php
namespace Drupal\mymodule\Plugin\migrate\source;
use Drupal\migrate\Plugin\migrate\source\SourcePluginBase;
/**
* @MigrateSource(
* id = "external_api",
* source_module = "mymodule"
* )
*/
class ExternalApiSource extends SourcePluginBase {
public function getIds(): array {
return ['id' => ['type' => 'integer']];
}
public function fields(): array {
return [
'id' => 'Record ID',
'title' => 'Title',
'content' => 'Content',
'tags' => 'Tags (comma separated)',
];
}
protected function initializeIterator(): \Iterator {
$page = 0;
do {
$response = \Drupal::httpClient()->get(
'https://api.external.com/posts?page=' . $page,
['headers' => ['Authorization' => 'Bearer ' . $this->configuration['api_key']]]
);
$data = json_decode($response->getBody(), true);
$items = $data['items'];
foreach ($items as $item) {
yield $item;
}
$page++;
} while (!empty($items) && $page < $data['total_pages']);
}
}
Avoiding Duplicates When Re-running Migration
Use highwater mark or track_changes: true in the source configuration. Then only new or modified records are migrated. This is especially useful for incremental migration after the initial import. Example configuration:
highwaterProperty:
name: updated_at
alias: u
Media Files Migration and Incremental Import
Media files migration involves images and files migrated in a separate YAML config, then linked to entities via migration_lookup. Example file migration:
id: files_migration
source:
plugin: csv
path: 'public://import/files.csv'
process:
filename:
plugin: callback
callable: basename
source: file_url
uri:
plugin: download
source:
- file_url
- '@filename'
destination:
plugin: 'public://migrated'
destination:
plugin: 'entity:file'
In the node migration, the field_image field uses migration_lookup to reference the uploaded file.
What Is Included in Our Content Migration Services?
Deliverables: As part of a turnkey solution, we provide:
- Documentation of migration structure
- Repository access with configuration
- Performance optimization: batch size, highwater mark, parallel processing
- Training for your developers on using Migrate
- One month of support after launch
- Guarantee of uninterrupted site operation during migration
- Cost: $500 - $3000 depending on complexity
Timeline
Simple migration from CSV (500–5000 records) — 2–3 days. Complex migration from multiple sources with custom plugins and transformations — 1–2 weeks. Exact timeline determined during a free audit.
Why Migration with Migrate Is Better Than Manual Transfer?
Migration via Migrate is not only about speed but also integrity assurance. We use best practices: highwater mark, track_changes, continue-on-failure. Our engineers hold Drupal certifications and have experience with projects exceeding 500,000 content records. Contact us to discuss your task. Get a free migration consultation.







