drupal-migrations
Guide for Drupal migrations including content imports, data transformations, and migration debugging.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Guide for Drupal migrations including content imports, data transformations, and migration debugging.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
PHP security review and vulnerability scanning guide. Use for code review, security audits, and fixing vulnerabilities in PHP applications.
Perform Drupal database operations including backups, restores, queries, and migrations. Use for database maintenance, troubleshooting, and data operations.
Guide for building REST APIs, JSON:API endpoints, and external integrations in Drupal.
Guide for Drupal configuration management including Features, config splits, environment handling, and deployment workflows.
Use this skill when running Drush commands, clearing caches, managing configuration, updating databases, or performing any Drupal CLI operations. Trigger phrases include "drush", "clear cache", "cache clear", "config export", "config import", "database update", "feature revert".
Use this skill when implementing Drupal hooks, altering forms, modifying entities, or extending Drupal behavior. Trigger phrases include "hook", "alter", "form alter", "entity presave", "preprocess", "event subscriber".
| name | drupal-migrations |
| description | Guide for Drupal migrations including content imports, data transformations, and migration debugging. |
Comprehensive guide for building and debugging Drupal migrations.
modules/custom/mymodule_migrate/
├── mymodule_migrate.info.yml
├── mymodule_migrate.module
├── migrations/
│ ├── mymodule_users.yml
│ ├── mymodule_content.yml
│ └── mymodule_terms.yml
├── src/
│ └── Plugin/
│ └── migrate/
│ ├── source/
│ │ └── CustomSource.php
│ └── process/
│ └── CustomProcess.php
└── config/
└── install/
└── migrate_plus.migration.mymodule_users.yml
id: mymodule_articles_csv
label: 'Import articles from CSV'
migration_group: mymodule_content
source:
plugin: csv
path: data/imports/articles.csv
ids:
- id
header_row_count: 1
# Optional: define columns
column_names:
0:
id: 'Unique ID'
1:
title: 'Article title'
2:
body: 'Article body'
3:
date: 'Publication date'
process:
type:
plugin: default_value
default_value: article
title: title
body/value: body
body/format:
plugin: default_value
default_value: full_html
created:
plugin: format_date
source: date
from_format: 'Y-m-d'
to_format: 'U'
uid:
plugin: default_value
default_value: 1
destination:
plugin: 'entity:node'
id: mymodule_events_json
label: 'Import events from JSON API'
migration_group: mymodule_content
source:
plugin: url
data_fetcher_plugin: http
data_parser_plugin: json
urls:
- 'https://api.example.com/events'
item_selector: data
ids:
id:
type: integer
fields:
- name: id
label: 'Event ID'
selector: id
- name: title
label: 'Event Title'
selector: attributes/title
- name: start_date
label: 'Start Date'
selector: attributes/start
process:
type:
plugin: default_value
default_value: event
title: title
field_event_date/value:
plugin: format_date
source: start_date
from_format: 'Y-m-d\TH:i:s'
to_format: 'Y-m-d\TH:i:s'
destination:
plugin: 'entity:node'
id: mymodule_legacy_users
label: 'Import users from legacy database'
migration_group: mymodule_users
source:
plugin: mymodule_legacy_users
# Custom source plugin handles connection
process:
name: username
mail: email
status:
plugin: static_map
source: active
map:
1: 1
0: 0
default_value: 0
roles:
plugin: explode
source: role_names
delimiter: ','
destination:
plugin: 'entity:user'
# Default value
field_type:
plugin: default_value
default_value: 'article'
# Static mapping
field_status:
plugin: static_map
source: status_code
map:
A: 'active'
I: 'inactive'
P: 'pending'
default_value: 'unknown'
# Callback function
field_slug:
plugin: callback
callable: strtolower
source: title
# Multiple callbacks
field_clean_title:
- plugin: callback
callable: trim
- plugin: callback
callable: strip_tags
source: raw_title
# Concatenation
field_full_name:
plugin: concat
source:
- first_name
- last_name
delimiter: ' '
# Format conversion
field_date:
plugin: format_date
source: date_string
from_format: 'm/d/Y'
to_format: 'Y-m-d'
# Timestamp to date
field_created:
plugin: format_date
source: timestamp
from_format: 'U'
to_format: 'Y-m-d\TH:i:s'
# Handle multiple formats
field_date:
plugin: format_date
source: date
from_format:
- 'Y-m-d'
- 'm/d/Y'
- 'd.m.Y'
to_format: 'Y-m-d'
# Migration lookup (reference migrated entity)
field_author:
plugin: migration_lookup
migration: mymodule_users
source: author_id
no_stub: true
# Entity lookup by field value
field_category:
plugin: entity_lookup
entity_type: taxonomy_term
bundle_key: vid
bundle: categories
value_key: name
source: category_name
# Entity generate (create if not exists)
field_tags:
plugin: entity_generate
entity_type: taxonomy_term
bundle_key: vid
bundle: tags
value_key: name
source: tag_names
# Explode string to array
field_tags:
plugin: explode
source: tags_string
delimiter: ','
# Iterate and transform
field_images:
plugin: sub_process
source: images
process:
target_id:
plugin: migration_lookup
migration: mymodule_files
source: file_id
# Skip empty values
field_optional:
plugin: skip_on_empty
source: optional_field
method: process
# Skip row if condition met
source_filter:
plugin: skip_on_value
source: status
value: 'deleted'
method: row
# Skip process if empty
field_optional:
plugin: skip_on_empty
source: maybe_empty
method: process
# Default if null
field_count:
plugin: null_coalesce
source:
- count
- '@default_count'
default_value: 0
<?php
namespace Drupal\mymodule_migrate\Plugin\migrate\source;
use Drupal\migrate\Plugin\migrate\source\SqlBase;
use Drupal\migrate\Row;
/**
* Source plugin for legacy users.
*
* @MigrateSource(
* id = "mymodule_legacy_users",
* source_module = "mymodule_migrate"
* )
*/
class LegacyUsers extends SqlBase {
/**
* {@inheritdoc}
*/
public function query() {
return $this->select('legacy_users', 'u')
->fields('u', [
'id',
'email',
'first_name',
'last_name',
'status',
'created_at',
])
->condition('u.status', 'active');
}
/**
* {@inheritdoc}
*/
public function fields() {
return [
'id' => $this->t('User ID'),
'email' => $this->t('Email address'),
'first_name' => $this->t('First name'),
'last_name' => $this->t('Last name'),
'status' => $this->t('Status'),
'created_at' => $this->t('Created date'),
];
}
/**
* {@inheritdoc}
*/
public function getIds() {
return [
'id' => [
'type' => 'integer',
'alias' => 'u',
],
];
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
// Generate username from email
$email = $row->getSourceProperty('email');
$username = explode('@', $email)[0];
$row->setSourceProperty('username', $username);
// Format full name
$fullName = trim(
$row->getSourceProperty('first_name') . ' ' .
$row->getSourceProperty('last_name')
);
$row->setSourceProperty('full_name', $fullName);
return parent::prepareRow($row);
}
}
<?php
namespace Drupal\mymodule_migrate\Plugin\migrate\process;
use Drupal\migrate\MigrateExecutableInterface;
use Drupal\migrate\ProcessPluginBase;
use Drupal\migrate\Row;
/**
* Transforms legacy codes to a new format.
*
* @MigrateProcessPlugin(
* id = "mymodule_code_transform"
* )
*
* Usage:
* @code
* process:
* field_code:
* plugin: mymodule_code_transform
* source: legacy_code
* @endcode
*/
class CodeTransform extends ProcessPluginBase {
/**
* {@inheritdoc}
*/
public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (empty($value)) {
return NULL;
}
// Transform legacy codes to new format
// Example: "OLD-12345" -> "NEW-12345"
$prefix = $this->configuration['prefix'] ?? 'NEW';
if (preg_match('/^[A-Z]+-(.+)$/', $value, $matches)) {
$value = $prefix . '-' . $matches[1];
}
return strtoupper($value);
}
}
# List all migrations
./vendor/bin/drush migrate:status
# Run migration
./vendor/bin/drush migrate:import mymodule_users
# Run with limit
./vendor/bin/drush migrate:import mymodule_users --limit=100
# Run with feedback
./vendor/bin/drush migrate:import mymodule_users --feedback=50
# Update existing (re-import changed)
./vendor/bin/drush migrate:import mymodule_users --update
# Rollback
./vendor/bin/drush migrate:rollback mymodule_users
# Reset stuck migration
./vendor/bin/drush migrate:reset-status mymodule_users
# View messages/errors
./vendor/bin/drush migrate:messages mymodule_users
# Stop running migration
./vendor/bin/drush migrate:stop mymodule_users
# Run group
./vendor/bin/drush migrate:import --group=mymodule_content
# Rollback group
./vendor/bin/drush migrate:rollback --group=mymodule_content
./vendor/bin/drush php:eval "
\$migration = \Drupal::service('plugin.manager.migration')
->createInstance('mymodule_users');
\$source = \$migration->getSourcePlugin();
\$count = 0;
foreach (\$source as \$row) {
print_r(\$row->getSource());
if (++\$count >= 3) break;
}
"
./vendor/bin/drush php:eval "
\$migration = \Drupal::service('plugin.manager.migration')
->createInstance('mymodule_users');
\$source = \$migration->getSourcePlugin();
\$executable = new \Drupal\migrate\MigrateExecutable(\$migration);
foreach (\$source as \$row) {
\$executable->processRow(\$row);
print_r(\$row->getDestination());
break;
}
"
# PostgreSQL
./vendor/bin/drush sql:query "SELECT * FROM migrate_map_mymodule_users LIMIT 10"
# MySQL
./vendor/bin/drush sql:query "SELECT * FROM migrate_map_mymodule_users LIMIT 10"
| Error | Cause | Solution |
|---|---|---|
| Source not found | Wrong path/URL | Check path or urls in source |
| Destination entity not found | Missing bundle | Check destination plugin type |
| Process plugin not found | Typo in plugin name | Check plugin ID spelling |
| Required field missing | Empty source | Add skip_on_empty or default |
| Entity reference invalid | Target not migrated | Add migration_lookup with dependency |
id: mymodule_articles
label: 'Articles'
migration_dependencies:
required:
- mymodule_users # Authors must exist
- mymodule_categories # Terms must exist
optional:
- mymodule_files # Files can be added later
process:
uid:
plugin: migration_lookup
migration: mymodule_users
source: author_id