Migration
geofield_migration.yml
field_geofield:
plugin: geofield_latlon
source:
- field_latitude
- field_longitude
15.01.2026 | Lothar Ferreira Neumann
How to migrate latitude & longitude geodata into a unified geocode field
query_manipulate.php
<?php
namespace Drupal\my_module_migrate\Plugin\migrate\source\d8;
use Drupal\migrate\Row;
/**
* Base class for D8 source plugins to collect field values from Field API.
*
* Available configuration keys:
* - id: (optional) The id of the content which should get migrated. This can
* be useful to only migrate a selected set of nodes. Excepts multiple ids.
*
* @MigrateSource(
* id = "my_module_d8_gallery",
* source_provider = "my_module_migrate"
* )
*/
class MyModuleGallery extends ContentEntity {
/**
* {@inheritdoc}
*/
public function query() {
$query = parent::query();
if (isset($this->configuration['id'])) {
$entityDefinition = $this->entityTypeManager->getDefinition($this->configuration['entity_type']);
$idKey = $entityDefinition->getKey('id');
$query->condition("d.{$idKey}", $this->configuration['id'], 'IN');
}
// Left join to get the nid of the old image node.
$query->leftJoin('paragraph__field_referenzfeld_galerie', 'pfrg', 'pfrg.entity_id = b.id AND pfrg.revision_id = b.revision_id');
$query->leftJoin('node__field_gallery_bild', 'nfgb', 'nfgb.entity_id = pfrg.field_referenzfeld_galerie_target_id');
$query->leftJoin('node__field_base_public_title', 'nfbpt', 'nfbpt.entity_id = pfrg.field_referenzfeld_galerie_target_id');
$query->addExpression('GROUP_CONCAT(nfgb.field_gallery_bild_target_id)', 'gallery_image_ids');
// Add public title field.
$query->addField('nfbpt', 'field_base_public_title_value', 'field_base_public_title_value');
$query->groupBy('b.id');
return $query;
}
/**
* {@inheritdoc}
*/
public function prepareRow(Row $row) {
$return = parent::prepareRow($row);
$nids = [];
$ids_string = $row->getSourceProperty('gallery_image_ids');
if (!empty($ids_string)) {
$ids = explode(',', $ids_string);
foreach ($ids as $id) {
$nids[] = ['nid' => (int) $id];
}
// Add new source property to skip old gallery node within migration.
$row->setSourceProperty('field_referenzfeld_galerie', $nids);
}
$row->setSourceProperty('field_base_public_title', $row->getSourceProperty('field_base_public_title_value'));
return $return;
}
}
query.sql
SELECT
GROUP_CONCAT(nfgb.field_gallery_bild_target_id),
nfbpt.field_base_public_title_value
FROM paragraph__field_referenzfeld_galerie AS pfrg
LEFT JOIN node__field_gallery_bild AS nfgb
ON nfgb.entity_id = pfrg.field_referenzfeld_galerie_target_id
LEFT JOIN node__field_base_public_title AS nfbpt
ON nfbpt.entity_id = pfrg.field_referenzfeld_galerie_target_id
WHERE pfrg.entity_id = 100810 AND pfrg.revision_id = 1919384
10.07.2025 | Lothar Ferreira Neumann
Optimizing Drupal Migration: Using GROUP_CONCAT for Data Aggregation
cleanup_aliases.php
<?php
/** @var \Drupal\path_alias\PathAliasStorage */
$alias_storage = \Drupal::entityTypeManager()->getStorage('path_alias');
$db = \Drupal::database();
$langcodes = array_keys(\Drupal::languageManager()->getLanguages());
$total = 0;
$duplicates = "";
// Get a list of all distinct aliases.
$query = $db->select('path_alias', 'pa')
->fields('pa', ['alias'])
->orderBy('alias')
->distinct();
$all_aliases = $query->execute()->fetchAll(\Pdo::FETCH_COLUMN);
foreach ($all_aliases as $alias) {
// Some aliases are 'NULL'.
if (!$alias) {
continue;
}
// We only look for duplicates within the langcode.
foreach ($langcodes as $langcode) {
// (1) Get the newest ID for that alias and langcode.
$query = $db->select('path_alias', 'pa')
->fields('pa', ['id'])
->condition('alias', $alias)
->condition('langcode', $langcode)
->orderBy('id', 'DESC')
->range(0, 1);
$newest_id = $query->execute()->fetchField(0);
if (!$newest_id) {
// There is no alias for that langcode.
// So there are also no duplicates.
continue;
}
// (2) Select all ids lower than it with the same alias and langcode.
$query = $db->select('path_alias', 'pa')
->fields('pa', ['id'])
->condition('alias', $alias)
->condition('langcode', $langcode)
->condition('id', $newest_id, '<');
$duplicate_ids = $query->execute()->fetchAll(\PDO::FETCH_COLUMN);
if (!str_starts_with($alias, "/")) {
$query = $db->select('path_alias', 'pa')
->fields('pa', ['id'])
->condition('alias', "/" . $alias)
->condition('langcode', $langcode);
$ids_with_slash = $query->execute()->fetchAll(\PDO::FETCH_COLUMN);
if (count($ids_with_slash) > 0) {
// We can safely delete the one without.
$to_delete = $alias_storage->loadByProperties([
'alias' => $alias,
'langcode' => $langcode,
]);
$alias_storage->delete($to_delete);
} else {
// It's the only one? Update with slash.
$alias_entitys = $alias_storage->loadByProperties([
'alias' => $alias,
'langcode' => $langcode,
]);
if (!empty($alias_entitys)) {
$alias_entity = reset($alias_entitys);
$alias_entity->set('alias', '/' . $alias);
$alias_entity->save();
}
}
}
// We delete those with the alias storage since we want Drupal to handle it
// and it will delete revisions as well.
$duplicate_aliases = $alias_storage->loadMultiple($duplicate_ids);
$total += count($duplicate_aliases);
if (count($duplicate_aliases) > 0) {
$duplicates = $duplicates . $alias . PHP_EOL;
}
$alias_storage->delete($duplicate_aliases);
}
}
echo(sprintf("Deleting %d duplicate aliases..." . PHP_EOL, $total));
echo($duplicates);
echo("DONE" . PHP_EOL);
21.02.2025 | Nikolas Kopp
Clean up migrated path aliases
query_range.php
// ...
$query->range(0, 10);
return $query;
05.09.2025 | Lothar Ferreira Neumann
How to perform a partial migration to save time in testing scenarios
migration_field_merge.yml
field_media:
- plugin: merge
source:
- field_image
- field_images
- plugin: sub_process
process:
target_id:
- plugin: migration_lookup
source: fid
migration:
- my_migration_media_image
21.05.2026 | Lothar Ferreira Neumann
Aggregate heterogeneous media fields for structured Drupal migration
drush_migrate_status.sh
drush migrate:statusDisplay a summary of the status of your migrations.
drush_migrate_import.sh
drush migrate:importTrigger the import process for migrations.
drush_migrate_reset_status.sh
drush migrate:reset-statusReset the migration statuses if your migration get stuck.
drush_migrate_rollback.sh
drush migrate:rollbackRollback your migrations by undoing changes made.
drush_migrate_stop.sh
drush migrate:stopStop a running migration gracefully.
drush_migrate_field_source.sh
drush migrate:fields-sourceList field sources of your sources plugin.
drush_migrate_message.sh
drush migrate:messagesShow migration messages.
17.05.2024 | Michael Ebert