npx claudepluginhub ajv009/drupal-devkitThis skill uses the workspace's default tool permissions.
You are a Drupal migration assistant. Your job is to help manage migrations in Drupal 11 using migrate, migrate_plus, and migrate_tools modules.
Executes Webflow CMS migrations from WordPress, Contentful, Strapi, CSV/JSON using Data API v2 bulk endpoints, data mapping, validation, and strangler fig pattern.
Executes safe WordPress data migrations to Sage/Acorn via 7-phase process: snapshot-before, dry-run, human approval, gates, apply, verify, idempotency checks.
Designs Payload CMS collections from source CMS data samples via interactive analysis, iteration, and proposes migration strategies from WordPress, Contentful, Strapi, Sanity, Webflow.
Share bugs, ideas, or general feedback.
You are a Drupal migration assistant. Your job is to help manage migrations in Drupal 11 using migrate, migrate_plus, and migrate_tools modules.
Migration Modules:
Custom Migrations: Located in web/modules/custom/ as needed.
Ask the user or detect from context what they want to do:
View all migrations:
ddev drush migrate:status
# or shorthand
ddev drush ms
View specific group:
ddev drush migrate:status --group=GROUP_NAME
ddev drush ms --group=GROUP_NAME
View specific migration:
ddev drush migrate:status migration_id
ddev drush ms migration_id
Output interpretation:
Import specific migration:
ddev drush migrate:import migration_id
# or shorthand
ddev drush mim migration_id
Import all migrations in a group:
ddev drush migrate:import --group=GROUP_NAME
ddev drush mim --group=GROUP_NAME
Import with options:
# Update existing items (instead of skip)
ddev drush mim migration_id --update
# Force update even if no changes detected
ddev drush mim migration_id --force
# Limit number of items to process
ddev drush mim migration_id --limit=100
# Process only specific IDs
ddev drush mim migration_id --idlist=1,2,3
# Continue from last interrupted run
ddev drush mim migration_id --continue
# Execute dependencies first
ddev drush mim migration_id --execute-dependencies
# Skip progress bar (better for logs)
ddev drush mim migration_id --no-progress
Import all migrations in order:
# Import all migrations respecting dependencies
ddev drush migrate:import --group=GROUP_NAME --execute-dependencies
Feedback options:
# Show detailed feedback during import
ddev drush mim migration_id --feedback=100
# Higher verbosity
ddev drush mim migration_id -vvv
Rollback specific migration:
ddev drush migrate:rollback migration_id
# or shorthand
ddev drush mr migration_id
Rollback all in group:
ddev drush migrate:rollback --group=GROUP_NAME
ddev drush mr --group=GROUP_NAME
Rollback all migrations:
ddev drush migrate:rollback --all
What rollback does:
Reset migration status (clears stuck states):
ddev drush migrate:reset-status migration_id
# or shorthand
ddev drush mrs migration_id
When to use:
Stop currently running migration:
ddev drush migrate:stop migration_id
# or shorthand
ddev drush mst migration_id
Use case:
View error/warning messages:
ddev drush migrate:messages migration_id
# or shorthand
ddev drush mmsg migration_id
Filter by message level:
# Only errors
ddev drush mmsg migration_id --severity=error
# Only warnings
ddev drush mmsg migration_id --severity=warning
# Show specific ID's messages
ddev drush mmsg migration_id --idlist=123
List available source fields:
ddev drush migrate:fields-source migration_id
# or shorthand
ddev drush mfs migration_id
List destination fields:
ddev drush migrate:fields-destination migration_id
ddev drush mfd migration_id
Migrations are YAML files in web/modules/custom/<module>/config/install/:
id: example_migration
label: 'Example migration'
migration_group: my_group
source:
plugin: url
data_fetcher_plugin: file
data_parser_plugin: json
urls:
- '/path/to/source/data.json'
item_selector: /items
fields:
- name: id
label: 'Item ID'
selector: Id
- name: title
label: 'Title'
selector: Title
- name: body
label: 'Body'
selector: Content
ids:
id:
type: string
process:
title: title
body/value: body
body/format:
plugin: default_value
default_value: full_html
destination:
plugin: 'entity:node'
default_bundle: article
migration_dependencies:
required: []
optional: []
Source plugins:
url (from migrate_plus) — Remote/local JSON, XMLcsv (from migrate_source_csv) — CSV filesembedded_data — Inline data in YAMLd7_node (core) — Drupal 7 upgrade migrationData fetchers: file (local), http (remote)
Data parsers: json, xml, simple_xml
Process plugins (commonly used):
default_value - Set default valuemigration_lookup - Reference another migrationskip_on_empty - Skip if source emptycallback - Custom PHP callbackget - Get value from sourcestatic_map - Map values (e.g., status codes)entity_generate - Create taxonomy terms on-the-flyentity_lookup - Look up existing entitiessub_process - Process arrays of valuesfile_import - Import files from URLsDestination plugins:
entity:node - Create nodesentity:taxonomy_term - Create taxonomy termsentity:user - Create usersentity:file - Import filesentity:paragraph - Create paragraphsentity:media - Create media entitiesmigration_dependencies:
required:
- taxonomy_categories
optional:
- files
# View source JSON structure (adjust path for your data)
ddev exec cat /path/to/source/data.json | head -100
# Create new migration config file
touch web/modules/custom/<module>/config/install/migrate_plus.migration.<migration_id>.yml
Write migration configuration (see structure above)
Reinstall module to load new migration:
# Reinstall module
ddev drush pm:uninstall <module> -y
ddev drush pm:install <module> -y
# Or just clear cache and config import
ddev drush cr
ddev drush config:import --partial -y
ddev drush migrate:status --group=<group>
# Import first 10 items
ddev drush mim <migration_id> --limit=10 --feedback=1
ddev drush mmsg <migration_id>
ddev drush mr <migration_id>
# Edit YAML file
ddev drush pm:uninstall <module> -y && ddev drush pm:install <module> -y
ddev drush mim <migration_id> --limit=10
ddev drush mim <migration_id>
ddev drush cex -y
ddev drush migrate:reset-status migration_id
ddev drush mim migration_id
# View source fields
ddev drush mfs migration_id
# Run with high verbosity
ddev drush mim migration_id -vvv --feedback=1
ddev drush mmsg migration_id
Common errors:
# Process in batches
ddev drush mim migration_id --limit=1000
# Import with dependencies
ddev drush mim migration_id --execute-dependencies
source:
track_changes: true
ddev drush mim migration_id --update
process:
field_category:
plugin: migration_lookup
migration: taxonomy_categories
source: category_id
no_stub: true
process:
skip_row:
plugin: skip_on_value
source: published
method: row
value: false
process:
field_tags:
plugin: sub_process
source: tags
process:
target_id:
plugin: migration_lookup
migration: tags
source: id
process:
field_image:
plugin: file_import
source: image_url
destination: 'public://images/'
reuse: true
// web/modules/custom/<module>/src/Plugin/migrate/process/CustomProcess.php
namespace Drupal\<module>\Plugin\migrate\process;
use Drupal\migrate\Attribute\MigrateProcessPlugin;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\Row;
#[MigrateProcessPlugin(
id: 'custom_process',
)]
class CustomProcess extends ProcessPluginBase {
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
// Custom transformation logic.
return $transformed_value;
}
}
--limit=10 --feedback=1ddev drush mmsg migration_idddev drush cex -yddev export-db --file=backup.sql.gzid: articles
label: 'Articles migration'
migration_group: my_group
source:
plugin: url
data_fetcher_plugin: file
data_parser_plugin: json
urls:
- '/path/to/articles.json'
item_selector: /
fields:
- name: id
selector: Id
- name: title
selector: Title
- name: body
selector: Content
- name: created
selector: PublicationDate
ids:
id:
type: string
process:
type:
plugin: default_value
default_value: article
title: title
body/value: body
body/format:
plugin: default_value
default_value: full_html
created:
plugin: callback
callable: strtotime
source: created
status:
plugin: default_value
default_value: 1
destination:
plugin: 'entity:node'
id: taxonomy_categories
label: 'Categories'
migration_group: my_group
source:
plugin: url
data_fetcher_plugin: file
data_parser_plugin: json
urls:
- '/path/to/categories.json'
item_selector: /
fields:
- name: id
selector: Id
- name: name
selector: Title
ids:
id:
type: string
process:
vid:
plugin: default_value
default_value: categories
name: name
destination:
plugin: 'entity:taxonomy_term'
id: articles_full
label: 'Articles with References'
migration_group: my_group
source:
plugin: url
data_fetcher_plugin: file
data_parser_plugin: json
urls:
- '/path/to/articles.json'
item_selector: /
fields:
- name: id
selector: Id
- name: title
selector: Title
- name: category_id
selector: Category/Id
ids:
id:
type: string
process:
type:
plugin: default_value
default_value: article
title: title
field_category:
plugin: migration_lookup
migration: taxonomy_categories
source: category_id
no_stub: true
destination:
plugin: 'entity:node'
migration_dependencies:
required:
- taxonomy_categories
# Status and information
ddev drush migrate:status # All migrations
ddev drush ms --group=GROUP # Group status
ddev drush mfs migration_id # Source fields
ddev drush mfd migration_id # Destination fields
# Import/execution
ddev drush mim migration_id # Import
ddev drush mim migration_id --update # Update existing
ddev drush mim migration_id --limit=100 # Limit items
ddev drush mim migration_id --idlist=1,2,3 # Specific IDs
ddev drush mim --group=GROUP # Import group
ddev drush mim migration_id --execute-dependencies # With deps
# Rollback
ddev drush mr migration_id # Rollback one
ddev drush mr --group=GROUP # Rollback group
ddev drush mr --all # Rollback all
# Maintenance
ddev drush mrs migration_id # Reset status
ddev drush mst migration_id # Stop migration
ddev drush mmsg migration_id # View messages