B A C K
How to Migrate Paragraphs from Drupal 7 to Drupal 8

How to Migrate Paragraphs from Drupal 7 to Drupal 8

Knowledge Centre

A few days ago, we encountered an exciting challenge: migrating paragraphs from a Drupal 7 platform to Drupal 8. While searching for the right solution, we scoured the web but found nothing that perfectly fit our needs. As a result, our backend developers created a custom solution. Special thanks to mtech-llc.com and Ada Hernández, whose work on field collection migration inspired this solution.

While Ada's original article describes migrating field collections, we adapted the approach for paragraphs migration. Her insights proved invaluable to our process.

Before proceeding, ensure you have:

1. A functional Drupal 8 installation

2. The following modules installed:

migrate_drupal

migrate_plus

migrate_tools

migrate_drupal_ui

Alternatively, you can add these as dependencies to your custom module.

After installing the required modules, create a paragraph with bundle "contact" containing these fields:

Text plain (machine name: field_name)

Email (machine name: field_email)

Number (Integer) (machine name: field_phone)

Then create a content type called Organization (machine name: organization) with:

Default body field

Field paragraph type (machine name: field_contact)

This procedure is complex and requires careful attention to detail. If errors occur during any step, you may need to start over. Please read each step thoroughly before proceeding.

1. Specify Database Connection in settings.php

Add the second connection to Drupal 7 in your settings.php file. In this example, the database name is "drupal_7_56":

  • $databases['migrate']['default'] = array(
  • 'database' => 'drupal_7_56',
  • 'username' => 'root',
  • 'password' => '',
  • 'prefix' => '',
  • 'host' => '127.0.0.1',
  • 'port' => '33067',
  • 'namespace' => 'Drupal\\Core\\Database\\Driver\\mysql',
  • 'driver' => 'mysql',
  • );

2. Create a Custom Module

Create a custom module with the machine name "custom".

3. Create YML Templates

Place the following YML templates in the config/install folder:

First, create the paragraph migration template (filename: migrate_plus.migration.d7_paragraph_contact.yml):

  • langcode: en
  • status: true
  • dependencies: {}
  • id: d7_paragraph_contact
  • class: null
  • field_plugin_method: null
  • cck_plugin_method: null
  • migration_tags:
  • - 'Drupal 7'
  • migration_group: migrate_drupal_7
  • label: Contacts
  • source:
  • plugin: d7_paragraph_item
  • key: migrate
  • field_name: field_contact
  • process:
  • field_name:
  • plugin: iterator
  • source: field_name
  • process:
  • value: value
  • revision_id: revision_id
  • field_email:
  • plugin: iterator
  • source: field_email
  • process:
  • value: email
  • revision_id: revision_id
  • field_phone:
  • plugin: iterator
  • source: field_phone
  • process:
  • value: value
  • revision_id: revision_id
  • destination:
  • plugin: 'entity_reference_revisions:paragraph'
  • default_bundle: contact
  • migration_dependencies:
  • required: {}
  • optional: {}

Then create the node migration template (filename: migrate_plus.migration.d7_node_organization.yml):

  • langcode: en
  • status: true
  • dependencies: {}
  • id: d7_node_organization
  • class: null
  • field_plugin_method: null
  • cck_plugin_method: null
  • migration_tags:
  • - 'Drupal 7'
  • - Content
  • migration_group: migrate_drupal_7
  • label: 'Nodes (Organization)'
  • source:
  • plugin: d7_node
  • node_type: organization
  • process:
  • nid: nid
  • vid: vid
  • langcode:
  • plugin: default_value
  • source: language
  • default_value: und
  • title: title
  • uid: node_uid
  • status: status
  • created: created
  • changed: changed
  • promote: promote
  • sticky: sticky
  • revision_uid: revision_uid
  • revision_log: log
  • revision_timestamp: timestamp
  • body:
  • plugin: iterator
  • source: body
  • process:
  • value: value
  • format:
  • - plugin: static_map
  • bypass: true
  • source: format
  • map:
  • - null
  • - plugin: skip_on_empty
  • method: process
  • - plugin: migration
  • migration:
  • - d6_filter_format
  • - d7_filter_format
  • source: format
  • field_contact:
  • - plugin: skip_on_empty
  • method: process
  • source: field_contact
  • - plugin: migration_lookup
  • migration: d7_paragraph_contact
  • no_stub: true
  • - plugin: iterator
  • process:
  • target_id: '0'
  • target_revision_id: '1'
  • destination:
  • plugin: 'entity:node'
  • default_bundle: organization
  • migration_dependencies:
  • required: {}
  • optional: {}

4. Create the Source Plugin Class

Create a file named ContactParagraph.php in the /src/Plugin/migrate/source directory:

  • <?php
  • namespace Drupal\mparagraf\Plugin\migrate\source;
  • use Drupal\migrate\Row;
  • use Drupal\migrate_drupal\Plugin\migrate\source\d7\FieldableEntity;
  • /**
  • * D7_paragraph_item source.
  • *
  • * @MigrateSource(
  • * id = "d7_paragraph_item"
  • * )
  • */
  • class ContactParagraph extends FieldableEntity {
  • /**
  • * {@inheritdoc}
  • */
  • public function query() {
  • // Select node in its last revision.
  • $query = $this->select('paragraphs_item', 'fci')
  • ->fields('fci', [
  • 'item_id',
  • 'field_name',
  • 'revision_id',
  • ]);
  • if (isset($this->configuration['field_name'])) {
  • $query->innerJoin('field_data_' . $this->configuration['field_name'], 'fd', 'fd.' . $this->configuration['field_name'] . '_value = fci.item_id');
  • $query->fields('fd', [
  • 'entity_type',
  • 'bundle',
  • 'entity_id',
  • $this->configuration['field_name'] . '_revision_id',
  • ]);
  • $query->condition('fci.field_name', $this->configuration['field_name']);
  • }
  • return $query;
  • }
  • /**
  • * {@inheritdoc}
  • */
  • public function prepareRow(Row $row) {
  • // If field specified, get field revision ID so there aren't issues mapping.
  • if (isset($this->configuration['field_name'])) {
  • $row->setSourceProperty('revision_id', $row->getSourceProperty($this->configuration['field_name'] . '_revision_id'));
  • }
  • // Get field API field values.
  • foreach (array_keys($this->getFields('paragraphs_item', 'contact')) as $field) {
  • $item_id = $row->getSourceProperty('item_id');
  • $revision_id = $row->getSourceProperty('revision_id');
  • $row->setSourceProperty($field, $this->getFieldValues('paragraphs_item', $field, $item_id, $revision_id));
  • }
  • return parent::prepareRow($row);
  • }
  • /**
  • * {@inheritdoc}
  • */
  • public function fields() {
  • $fields = [
  • 'item_id' => $this->t('Item ID'),
  • 'revision_id' => $this->t('Revision ID'),
  • 'field_name' => $this->t('Name of field'),
  • ];
  • return $fields;
  • }
  • /**
  • * {@inheritdoc}
  • */
  • public function getIds() {
  • $ids['item_id']['type'] = 'integer';
  • $ids['item_id']['alias'] = 'fci';
  • return $ids;
  • }
  • }

5. Implement the Migration Hook

Add the following code to your module's .module file:

  • <?php
  • use Drupal\migrate\Plugin\MigrationInterface;
  • use Drupal\migrate\Plugin\MigrateSourceInterface;
  • use Drupal\migrate\Row;
  • /**
  • * Implements hook_migrate_MIGRATION_ID_prepare_row().
  • */
  • function custom_migrate_d7_node_organization_prepare_row(Row $row, MigrateSourceInterface $source, MigrationInterface $migration) {
  • $values = $row->getSourceProperty('field_contact');
  • $value_new = [];
  • if ($values) {
  • foreach ($values as $value) {
  • $value_new[] = ['item_id' => $value['value']];
  • }
  • $row->setSourceProperty('field_contact', $value_new);
  • }
  • }

6. Final Steps

Once you've completed all the above steps successfully, you can run your migration and watch as your paragraphs are transferred from Drupal 7 to Drupal 8. Take a moment to verify the results before proceeding with any additional paragraph-related tasks.

Remember to test thoroughly in a development environment before attempting this migration on a production site.