Migrating any type of block to Layout Builder in Drupal

Fabian Fiorotto
3 min readApr 21, 2023

--

Introduction

I recently found myself with the task of migrating blocks to Drupal Layout Builder. Searching the internet I found this blog post. It was a good start but the solution proposed in that post wasn’t entirely useful for me for the following reasons:

  • I wasn’t migrating paragraphs but blocks from another CMS that has nothing to do with Drupal
  • That code creates a new section for each block and stacks them one below the other and I needed to place the blocks in a given section in the default layout.
  • I Needed to point to a different display view.

That is why I decided to code my own solution and share it with the community. You can see the code of these components in this gist. It isn’t the real code I used, I have pruned the migrations leaving only what is relevant for this example.

Other advantages of this solution

  • It doesn’t use events
  • It doesn’t use inheritance

Known limitations

  • It only allows modifying a single section of the default layout.
  • The way blocks are organized (its delta) can be improved.

General notions

Unlike the article cited above, in this approach, the order of the plugins is reversed. First, we are going to collect all the blocks from other migrations and then we are going to place them in their respective regions. This makes sense since the idea of a plugin is to transform an input value from the source into the output value that the field expects. Ignoring a plugin's input value can be a sign that your approach can be improved.

Migrating the blocks

The only goal of the BlockLayout plugin is to wrap the block in a component.

public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
if (!$value) {
return NULL;
}
if (!isset($this->delta)) {
$this->delta = 0;
}

$region = $this->configuration['region'];
$component = $this->createComponent($value, $this->delta++, $region);

if (!$component) {
return NULL;
}

return $component;
}

To achieve this we need to get the id of the migrated blocks on the target Drupal site. We don’t need to call the migration lookup directly inside our plugin, we can chain that migration_lookup plugin before ours in the yml. With this, we managed to decouple our plugin from the source migration. Now we can store the result in a temporal field to use later.

The following is an example of a temporary field to load accordion components and place them sidebar (region column_three).

process:
...
right_block_temps:
-
plugin: migration_lookup
source: src_page_right_components_ids
migration: block_accordion
no_stub: true
-
plugin: block_layout
region: column_three

Overwriting the default layout

Now we need to place the components in their respective region. The plugin DefaultLayout gets the default theme iterates the regions and components and when the region matches it places the component.

public function transform($value, MigrateExecutableInterface $migrate_executable, Row $row, $destination_property) {
$components = [];
$this->flatten($value, $components);
$bundle = $this->configuration['bundle'];
$section_label = $this->configuration['section'];
$display = $this->configuration['display'] ?? 'default';
if ($bundle) {
$sections = $this->loadDefaultSections($bundle, $display);
if (!empty($sections)) {

$section = NULL;
foreach ($sections as $sec) {
if ($sec->getLayoutSettings()['label'] == $section_label) {
$section = $sec;
}
}

if ($section) {
foreach ($components as $component) {
$section->appendComponent($component);
}
}

return $sections;
}
else {
return NULL;
}
}
return $value;
}

To overwrite the default layout the first thing we need to do is use the get plugin to collect all the temporary fields of the different types of blocks that we want to migrate.

That will result in a nested array which we need to flatten. The flatten plugin that comes with drupal_migrate doesn't work because it expects an array of primitives. If you want, you can create a new flatten plugin for objects. In this case, I’ll leave it in the same plugin for simplicity.

The following is an example of how to load different blocks within the block layout. The plugin receives the destination bundle and the display as parameters. We can use this data to obtain the default layout that will be used as a base. It also receives the destination section, since the sections in Drupal don’t have an id or machine name I had to use the name.

process:
...
layout_builder__layout:
-
plugin: get
source:
- '@center_block_temps'
- '@right_block_temps'
- '@downloads_block_temp'
-
plugin: default_layout
bundle: article
display: full
section: Sidebars

--

--