Dev030 iconDev030Sep 1, 2026 ~5 min source read

Migrating page element configuration from Panels to Layout Builder

A practical summary of how a Drupal 7 site’s custom page elements were exported and translated into Layout Builder block components without rebuilding pages by hand.

Share this story

Send the public story page.

Useful takeaways from this story.

Let target block plugins convert incoming configuration via a mapConfig method so conversion logic stays with each block.

Create Layout Builder SectionComponent objects in the same order as the source and store the source pane UUID so future sync runs update instead of duplicate.

Design the sync as a repeatable module that can change destinations (Paragraphs, blocks) while the source endpoint remains stable.

A rebuilt Drupal site had new block plugins and Layout Builder layouts, but the existing site still had many pages assembled with Panels and custom ctools page elements. Recreating those pages manually would be slow and risk losing configuration. The goal was to preserve page elements, their order, and as much configuration as the new blocks could use, without migrating complete Panels displays.

Source export: a minimal, safe JSON endpoint

Destination flexibility: Paragraphs first, then Layout Builder

Letting blocks translate their own configuration

Blocks on the new site that could accept sync data implemented a small interface with a static mapConfig method. That method received the Drupal 7 configuration, the page node, and the element type, and returned configuration suitable for the new block.

This pattern keeps element-specific conversion inside the block implementation rather than in the synchronization service. Blocks can handle simple rename/copy cases or complex conversions such as nested lists or replacement of old item types with new plugin-based items.

Example approach to nested conversions

When a block contained a configurable list of summary items, the old Drupal 7 items were identified by element type names. The new implementation used configuration-container item plugins. The block used a transition map that listed the target plugin for each old item type and any configuration adjustments. Unknown item types were skipped. If a block could not produce valid configuration it reported the source element as incomplete rather than creating a broken component.

After mapping an element, the synchronization service created a SectionComponent, appended it to the content region in the same order supplied by Drupal 7, and stored the Drupal 7 pane UUID inside the component sync config. Each component received a new local UUID as required by Layout Builder.

On subsequent runs the service searched for an existing component with the same source UUID. If found, it updated that component. Components created locally on the rebuilt site (with no source UUID) were not touched.

Operational design: repeatable, incremental, and safe

The migration was implemented as a temporary module called element_sync that could run repeatedly while both sites were available. That allowed incremental migration and iteration: blocks could be improved over time and the sync re-run to pick up better mappings. The service supported dry-run behavior in the mapConfig API so mappings could be validated before creating components.

  • The source stayed simple and stable: export only element type, configuration, and pane UUID. The export did not replicate full page layouts.
  • Conversion responsibilities were owned by the target block plugins, so the synchronization service remained generic.
  • Storing the source UUID on components preserved identity and prevented duplicate components when the sync ran multiple times.

Concrete next steps for teams attempting this

  • Build a minimal, secure export endpoint on the legacy site that returns element type, configuration, and a unique source UUID for each pane.
  • Implement a syncable block interface on target blocks with a mapConfig method that can operate in dry-run mode.
  • Create a repeatable synchronization service that appends SectionComponents in source order and stores source UUIDs so updates are possible rather than always adding new components.

More context around this story.

Loading more related stories...

Keep reading in the app

Open the app view to save this story, compare related coverage, and continue from the same source.

Open in app