Upgrade Your Drupal Skills

We trained 1,000+ Drupal Developers over the last decade.

See Advanced Courses NAH, I know Enough
Oct 29 2020
Oct 29

There have been many site-builder tools for building flexible layouts in Drupal including Field Collection, Panels, and Paragraphs. But only one of them is part of Drupal core: Layout Builder. Layout Builder provides a clean and user friendly drag-and-drop editorial experience. But don’t take my word for it. If you haven’t read the amazing blog post, The Big, Bad Layout Builder Explainer by Caroline Casals you owe it to yourself to do so. Go ahead, I’ll wait.

“I want to use Layout Builder, but my very large site has tens of thousands of Paragraphs, and you can’t migrate Paragraphs to Layout Builder”, you might be saying. Well let me tell you it is possible to do, and I have done it. In fact, I have migrated over 100,000 paragraphs into Layout Builder, and you can too!

A Warning

Migrating Paragraphs to Layout Builder is a sometimes difficult, often tedious process. There are several key challenges that can eat into your development time, making the case for moving to Layout Builder that much more of a hard sell to stakeholders.

One of such challenges is the often occurring Paragraph within Paragraph architecture. This is common as Paragraphs are often used for layout as well as structured content. For example, a three-column layout can be built with Paragraphs. Each column would have a corresponding Paragraphs entity reference revision field. Migrating these types of architectures is complex. Due to this we won’t cover them today, but once you get the hang of the process it should become clear where the customizations are needed to accomplish a migration with container Paragraphs.


For the purpose of this writeup we will assume you are migrating Paragraphs to Layout Builder on Nodes during a Drupal upgrade from D7 to D8. Since Layout Builder works with fieldable entities, it is possible to use Layout Builder for things like Menus as well, as you may have seen with Mike Potter’s blog post, Creating a Mega Menu using Layout Builder in Drupal 8.

In this writeup I will often refer to a repository which contains a full working example of a migration from Drupal 7 with Paragraphs to Drupal 8 with Layout Builder.


Before beginning, ensure that you have enabled Layout Builder for your content type using override section storage. Read about how to set up default layouts on Drupal.org. Modify the default layout by removing unwanted blocks or otherwise changing the layout per your specifications. In the example repo, I have set the body field to display in the default layout to demonstrate using default and override section storage together. More on this later.

Layout Builder Storage

To understand how the migration needs to be written, we must first understand how to store data in Layout Builder. Layout Builder doesn’t store data like a traditional Drupal field might. It uses what is called a “section”. Sections are composed of one or more regions, and in those regions are placed blocks of various types.

Layout Builder uses three types of Section storage: default, override, and temporary.

Default Section Storage

When you enable Layout Builder for a Content Type, you do so at the display mode administration UI at /admin/structure/types/manage/[content-type]/display. There you will find a ‘Use Layout Builder’ checkbox. When you check this box, you see a second checkbox appear, ‘Allow each content item to have its layout customized’. If you leave the second checkbox unchecked, it means that you will be using the same layout for each Node of this type in this display mode. This is the default Section storage: each Node has the same layout. Editors can not change the layout or add blocks to it on an individual Node basis. The default layout is stored in config for the entity view mode in third party settings.

Override Section Storage

By enabling the second checkbox mentioned above with the label ‘Allow each content item to have its layout customized’ you are telling Layout Builder that each entity can have a different layout and therefore is edible on an individual Node basis at the node/[nid]/layout tab. When enabling this storage mode, Layout Builder adds a field to the Node with a machine name of layout_builder__layout. This field stores the sections and other metadata used by Layout Builder in a serialized blob in the database.

Using override storage is a bit like using both default and override storage together. A default layout is used to create the initial layout of a new node, and override storage is what allows customizing this layout on a per-node basis. This combination is possible to retain during the migration as well, as we will see in a later section.

Temporary Section Storage

When editing a layout, prior to saving, the layout sections are stored in the core SharedTempStoreFactory service, similar to how Views stores unsaved changes. We will not be working with this type of storage during the migration process, but I pointed it out here so that you have a well rounded understanding of Layout Builder section storage.

Section Storage Construction

So we know that a layout contains one or more sections, sections contain regions, and regions contain blocks, but this is not the whole story, and in fact not exactly correct either. You see, a section is really a container for section components and other properties, and knowing how it comes together is crucial to writing the migration.

Let’s look at a node that has Layout Builder override storage enabled and see how it is constructed. We can see how the layout field data is constructed by loading the Node and calling the toArray() method on it. This gives us the exact pattern needed for the migration destination:


Screenshot of Layout Builder Array


The first element we encounter is the ‘layoutId’. Layouts are created as yml files and are stored at the root of a module or theme. You can read more about defining and building layouts on Drupal.org.

Next we see the ‘layoutSettings’. I won’t go into detail about layouts too much here, but know that the layout, template, and settings all define the behavior of the layout itself. For instance, for a multi-column layout, you might want to allow the editor to provide a width for each column, and layout settings are where you would do that. The settings are exposed in Drupal forms in the sidebar when adding or editing a section.

We will use the core-provided ‘layout_onecol’ layout: a single column layout with no configuration options, and a single region called ‘content’ for simplicity.


As you can see in the figure above, a component is a SectionComponent object, and a section can contain one or many components. Components are a metadata wrapper for a block: they store data about a block including the region of the layout it should be placed in, configuration, and weight.


A component ‘region’ tells the section where to place the component in the layout. Some layouts only have one region, in the case of the node we are viewing it is, ‘content’. For multi-column layouts, you might have ‘left’ and ‘right’ regions, or ‘first’, ‘second’, etc.


The ‘configuration’ array in a section component is the beating heart of the component. This specifies exactly what kind of block belongs in the region, and block display settings that tell Layout Builder how to render it. In this case, we are seeing an inline_block:basic block. This means that in this component will be placed a content block with a machine name of ‘basic’, with a revision id of 16966. This is the glue that tells Layout Builder to render a basic block with this revision id. If there were other settings that controlled the display of the block, they would be here as well.


Like other areas of Drupal, the components in a section’s region will be sorted by weight, allowing you to add them in any order provided you also supply the correct weight in the migration, should that be available.

Let's Get To It

Wow that is a lot of information! If you managed to get through all that, you are now armed with that enough to get started, so let’s dive into the good stuff.

The Process

There is no one-size-fits-all solution to migrating Paragraphs to Layout Builder. How you go about it depends on your situation, and it is going to require writing some code to get it done. Overall, however, there are a few necessary steps regardless of your path.

Paragraphs to Blocks

The first step in the process is to get your paragraphs migrated to blocks. There is no secret sauce here, it is just a basic migration. One thing that I would recommend is having a single migration for each Paragraph type, rather than a single monolithic migration that handles all of them. This makes writing any necessary logic for a given paragraph easier to target. And, you can use the migration group shared configuration for properties and fields that are common among all the paragraphs, if that is beneficial to you.

Obviously, before you can migrate the Paragraphs to blocks, you need to build and configure those blocks on the destination. You might end up with a one-to-one relationship between the Paragraphs bundles and block types, or you might take the opportunity to consolidate or otherwise change the content model. This is ok because at the end of the day we just need blocks, so however you arrive there will work for this migration.

Linked below is an example of a blockquote Paragraph migration that creates new content blocks with a machine name of ‘blockquote‘:


And the corresponding group migration configuration:


Reusable Blocks

If your site has many thousands of content blocks, consider setting the ‘reusable’ field to 0, or you may experience site performance issues. This is due to how block permissions are calculated on each page. By setting the reusable field  in your process mappings, the Block will become essentially invisible to Drupal for use anywhere. It will not appear in the Block layout UI, it will not appear in Layout Builder’s sidebar when adding a block, etc. And it means it will not be considered when core builds Block permissions or calculates region conditions, at least on pages where the Block isn’t used. Set the reusable field like so in your migration process configuration:

      plugin: default_value
      default_value: 0

Node Migration

Once the blocks are created by running the paragraph to block migrations, we are ready to write the Node migrations. The below example may be for a single content type, but this pattern can be applied to any or all of the Node bundles involved in your particular situation.

Here is the Article node migration used in the example repo:


And the related group migration configuration:


The important bits for Layout Builder migration are in this portion of the process configuration:

    plugin: default_layout
    bundle: article
    plugin: paragraphs_layout
    source_field: field_paragraphs
    plugin: get
      - '@default_temp'
      - '@paragraphs_temp'

Here we can see that there are two temporary field mappings which use custom process plugins: default_layout and paragraphs_layout, and we are adding the output of these two temporary fields to the layout_builder__layout field for the Article.

Layout Process Plugins

This is where the magic happens. The core of our customizations necessary for migrating Paragraphs to Layout Builder are “simply” a few migration process plugins. These plugins return sections that we store in temporary fields. We then assemble each temporary field in the order we want them displayed on the page by using them as sources for our layout_builder__layout destination field.

Default Layout Process Plugin

The default layout process plugin is very simple. As we have learned, the default layout is stored in configuration in the view mode of the entity as third party settings. So the job of this process plugin is to grab that configuration and return it. This is easily accomplished using the config factory service and Layout Builder’s API for converting an array into a section object:

$config = 
    $sections_array = $config->get('third_party_settings.layout_builder.sections');
    $sections = [];

    if (!empty($sections_array)) {
      foreach ($sections_array as $section_data) {
        $sections[] = Section::fromArray($section_data);
    return $sections;

You can find the default_layout process plugin code here in the example repo.

Paragraphs Layout Process Plugin

At last, the piece that brings everything together: the Paragraph process plugin. This plugin is doing some heavy lifting and because of this can get rather complicated very quickly. For this reason, the provided plugin I wrote handles “non-container” paragraphs only. We aren’t doing accordions or tabs or anything like that here, but as I mentioned earlier, it is possible to do. Perhaps I’ll touch on that in a future blog post.

At the end of the transform method, the process plugin needs to return one or more sections, and in order to do that, performs the following tasks:

  • Create a section.
  • Iterate over the source field values.
  • For each value, find the paragraph type.
  • Find the correct block migration for the identified paragraph type.
  • Find the migrated block.
  • Find the revision id of the block.
  • Create a component from the block information.
  • Append the component to the section.
  • Return the section.

You can find the paragraphs_layout process plugin code here in the example repo.

Create a Section

This is relatively straightforward since we have only one layout template to worry about. In the LayoutBase class, I have a method called createSection() which takes in several parameters with default values. Since we only have one layout in this migration, there is no need to pass in any values to this method, but it is written to be flexible enough to handle any type of layout required for your use case.

 public function createSection(array $components = [], $layout = 'layout_onecol', 
array $settings = []) {
    return new Section($layout, $settings, $components);

Find Paragraph Type

When iterating over the source field values, we have only the paragraph id and revision id to work with. Using that information, however, we can obtain the paragraph type for a given id by querying the source migration database. In the example below, the migrateDb property is a connection to the source database.

public function getParagraphType($id) {
    $types = &drupal_static(__FUNCTION__);
    if (!isset($types[$id])) {
      $query = $this->migrateDb->select('paragraphs_item', 'p');
      $query->fields('p', ['bundle']);
      $query->condition('p.item_id', $id, '=');
      $types[$id] = $query->execute()->fetchField();
    return $types[$id];

This method is found in the ParagraphsLayout process plugin class.

Create New Component

To create a new component for the section, we need to locate the block that was created during the related paragraph to block migration. To do this, we leverage the migrate lookup service provided by core. In order to do this, however, we need to know the machine name of the migration that corresponds to the paragraph type in question.

Since migrations store source to destination mappings id’s in a mapping table named for the migration involved, we can use the migrate lookup service to locate the block that was created during the migration. All we need to do this is the source id and the migration id. To make this process easy, I used the Article migration source configuration to store a mapping of the paragraph type to block migration id in the constants section.

  plugin: d7_node
  node_type: article
      block_quote: 'paragraphs_blockquote'
      rich_text: 'paragraphs_rich_text'

This mapping can be used in process plugins by pulling out the configuration like so:

    $map = $row->getSource()['constants']['map'];

Now that we have the migration id for the current paragraph type, we can lookup the block the migration created when it ran. The code from below is taken from the LayoutBase class:

public function lookupBlock($migration_id, $id) {
    // Find the block from the related migration.
    $source = [$id];
    $block_ids = $this->migrateLookup->lookup($migration_id, $source);
    if (empty($block_ids)) {
      throw new LayoutMigrationMissingBlockException(sprintf('Unable to find related migrated block for source id %s in migration %s', $id, $migration_id), MigrationInterface::MESSAGE_WARNING);
    return reset($block_ids)['id'];

If the block was located, the lookupBlock() method returns the id of the block, or throws a LayoutMigrationMissingBlockException which is a custom exception that we trap with a try/catch block in the ParagraphsLayout process plugin’s transform() method. The exception logs the missing block information to the migrate message table for the given node. Without this trapping and logging, this information would be lost and troubleshooting would be difficult.

Layout Builder needs a block revision id, not the block id, so we need to query this from the destination database. This and other information necessary for creating a new section component resides in the createComponent() method in the LayoutBase class:

 public function createComponent(LayoutMigrationItem $item, $region = 'content') {
    // Find the block from the related migration.
    $block_id = $this->lookupBlock($item->getMigration(), $item->getId());
    // Get the block type. Use a db query instead of loading the entity for
    // performance.
    $query = $this->db->select('block_content_field_data', 'b')
      ->fields('b', ['type'])
      ->condition('b.id', $block_id, '=');
    $block_type = $query->execute()->fetchField();
    if (!$block_type) {
      throw new MigrateException(sprintf('An unknown error occurred trying to find the block type from migration item type %s with id %s.', $item->getType(), $item->getId()));
    // Get the latest revision id for the block.
    $block_revision_id = $this->blockContentStorage->getLatestRevisionId($block_id);

    // Create a new component from the block.
    return $this->createSectionComponent($block_revision_id, $block_type, $item->getDelta(), $region);

Finally, we can create a new section component:

public function createSectionComponent($block_latest_revision_id, $block_type, $weight = 0, $region = 'content') {
    return SectionComponent::fromArray([
      'uuid' => $this->uuid->generate(),
      'region' => $region,
      'configuration' =>
          'id' => "inline_block:{$block_type}",
          'label' => 'Layout Builder Inline Block',
          'provider' => 'layout_builder',
          'label_display' => '0',
          'view_mode' => 'full',
          'block_revision_id' => $block_latest_revision_id,
          'block_serialized' => NULL,
          'context_mapping' => [],
      'additional' => [],
      'weight' => $weight,

This component will be appended to the section that is ultimately returned by the process plugin:


This process is repeated for each paragraph on the source Article node.

Section Flattening

The default_layout process plugin might return one or more sections, or if you modify the paragraphs_layout process plugin, it too may need to return more than one section. Because of this, we might end up with a value in the destination field that will not be expected by field storage, and therefore rejected entirely. To resolve this, I used an event subscriber that subscribes to the ‘pre row save’ event, and checks for the layout_builder__layout field in the destination. If found, it flattens the multidimensional array into the single dimension expected by entity field storage. I think the docblock for the subscriber method does a good job of explaining it further:

   * Migration pre-row save event subscriber.
   * This method is used to flatten the layout_builder__layout field into a
   * single-dimensional array. This is needed because some of the layout plugins
   * can add multiple sections to this field and this is not a structure
   * supported by the field. Consider the following array, where
   * [Layout Section] is a Drupal\layout_builder\Section object:
   * @code
   *   $layout_builder__layout => [
   *     0 => [Layout Section],
   *     1 => [
   *       0 => [Layout Section],
   *       1 => [Layout section],
   *     ],
   *     2 => [Layout Section],
   *   ];
   * @endcode
   * This method will produce a flattened layout field resulting in the
   * following:
   * @code
   *   $layout_builder__layout => [
   *     0 => [Layout Section],
   *     1 => [Layout Section],
   *     2 => [Layout Section],
   *     3 => [Layout section],
   *   ];
   * @endcode
   * @param \Drupal\migrate\Event\MigratePreRowSaveEvent $event
   *   A migration event.

All that is left is to run the Article migration either in the UI or using Drush.


This process, while contrived for a single content type with just a few paragraphs, can be applied to scale to whatever your architectural needs may require. The paragraphs_layout process plugin can be expanded upon to support additional use cases such as for migrating container paragraphs.

We have been on quite a journey from learning how Layout Builder storage works, how sections are constructed, and finally how to write the migration plugins for moving from Paragraphs to Layout Builder. This process, as you have seen, can range from semi-complex to extremely convoluted, and as such needs careful consideration before commitment. But if you are ready and willing to make the leap to core-provided flexible layouts, then I hope I have made that at least somewhat easier for you. Good luck with your migration, should you choose to embark on it!

May 06 2020
May 06

Drupal 9 Is Almost Here

Drupal 9.0.0 will officially drop on June 3, 2020. The good news (which we’ve already covered in detail here, and here) is that Drupal 9 will not be a significant upgrade from Drupal 8. 

To recap, it is effectively a version bump, with two caveats:

  • Symfony 4 will replace Symfony 3 as a core dependency. This will improve Drupal’s underlying core functionality, stability, and security.
  • Some Drupal core code that has already been marked as deprecated will be removed entirely. This code still exists in Drupal 8 to support previous upgrades from Symfony 2 in the past, and all deprecated code has Drupal 9 ready alternatives which should be used instead. There are automated tests that can assure any organization if the project is using any such deprecated code, and will even offer suggestions to replace it.

However, just because you’re on Drupal 8 now, don’t assume you’ll be ready to make the leap to 9 when June rolls around.

It’s More Than Code

While the code part is relatively easy to prepare for, the long-term viability of your Drupal 8 AND 9 CMS depends on you following Drupal best practices as well. An ideal Drupal site (i.e. one that will last the next two years and /or successfully make the leap to Drupal 9) will be one that leverages core functionality as much as possible.

Here are some of the more recent additions to Drupal core that all Drupal 8 sites should consider using in order to be ready for Drupal 9.

1) Paragraphs vs Panels vs Layout Builder

The ability for editors to layout content has been a long road. Panels was king, but Layout Builder has since replaced Panels. Layout Builder also has the added benefit of being part of Core, so it will work more closely with other core functionality.

The Paragraphs module is still very useful for content layout. However, the days of abusing Paragraphs for controlling layout are over. Start using it for what it was originally intended for, managing complex, tightly-bound bundles of content bits, but certainly not for actual page layout. There should be no more content-specific paragraphs within layout-specific paragraphs. 

This is an area where Layout Builder can help with content placement, while still using Paragraphs for content components.

2) Media Management

The Media module is now part of core, and most asset management in Drupal will begin there. Managing images, documents, and other digital assets as content types or custom entities should no longer be the pattern. Community support of core Media is ever-growing, and this is where the community’s focus will remain in the future.

3) Workflow: Workbench Moderation vs. Content Moderation

Editorial workflow is extremely important for many sites. Drupal 8 didn’t offer anything for this at first, and the community rallied around the ubiquity of Workbench Moderation. With the addition of Content Moderation in core, which is a very close facsimile of Workbench Moderation, this is where future attention will lie.

The Lightning install profile includes some tools to help transition from Workbench Moderation to to Content Moderation, so organizations don’t have to struggle alone in silence.

4) Modern Front-End Patterns

This is not strictly a Drupal 9 readiness point, but following good component-based design patterns is essential to maintaining a modern web platform. Using baseline tools such as Pattern Lab or Storybook is a great place to start to manage the components of a website. Drupal themes play nicely with these design implementation systems, often ingesting the same templates directly.

This sets up any Drupal site nicely for using component-based layout with Layout Builder and even Paragraphs-based content components.


Ensuring any Drupal project is not using deprecated code is the first step in Drupal 9 readiness. Even more important is ensuring that it is taking advantage of the latest core functionality and features, as these will be where the focus of the Drupal Contrib Community will be. Taking advantage of Core as much as possible will help with cases where once-common contrib modules are eventually abandoned over time.

Apr 17 2020
Apr 17

Those of us who have experience traveling the long dark roads of migration projects between Drupal’s previous major version releases may, understandably, be feeling some trepidation at the impending release of Drupal 9 (potentially as soon as June 3, 2020). 

The journey from Drupal 7 to Drupal 8 in particular was a major challenge for many developers, with massive API changes, a completely new theme layer, and a fundamental OOP architecture. 

The investment in these massive changes, however, is paying big dividends as we approach D9. This journey is going to be a lot easier in comparison. In fact, for many of us, the journey is nearly over before it begins.

D9 is D8 (mostly)

Here is the big takeaway: if you are on Drupal 8.8+, you are already fully API compatible with Drupal 9. Drupal 8’s OOP structure and its core Symfony foundation have made it possible for the Drupal API to be more stable and continuous between major versions. In fact, the major differences between Drupal 8 and Drupal 9 are the removal of deprecated code and the update of core dependencies so that they remain on supported versions.

If D9 is D8 (mostly), then why should I go through the trouble?

Drupal depends on third party libraries and frameworks, and those libraries and frameworks must be updated over time to maintain support and security updates.  Sometimes, it’s impossible to maintain backward compatibility with older versions of these dependencies. Drupal 8 adopted semantic versioning, which made it possible to introduce major new features and backward compatible third-party dependency updates in minor releases, while also making it easier to adopt backward compatibility breaking changes in a clear and predictable way.

Drupal 8’s central core dependency, Symfony 3.4, will stop receiving bug fixes in November 2020, and stop receiving security updates in November 2021. Drupal 9 will depend on Symfony 4.4, which is not fully backward compatible with Symfony 3.4. 

Your site needs to remain secure, so you need to adopt Drupal 9.  The good news is that even though Drupal 9 is planned for release June 3, 2020, Drupal 8.9x will remain supported until November 2021. This time around, we have a much lower level of effort to transition, and a long window of time to make it happen.

Dealing with deprecated code

In most cases, removing instances of deprecated code from your custom code is a simple process. Matt Glaman’s drupal-check tool can scan your code and return a report of any deprecated code in use. Many changes will be as simple as changing a call to: file_unmanaged_copy() to \Drupal::service(‘file_system’)->copy(). Some changes may require a little more work, but that work is being done within the same Drupal 8 API you’ve grown accustomed to.

For contributed code, identify the modules you rely on that use deprecated code, and use this as a great opportunity to contribute back to the community by testing and submitting patches.

The bottom line, however, is that you must deal with your deprecated code. It will not work in D9.

Dealing with core dependency updates

Here are some of the most important core dependency changes that may impact you.

  • Symfony 3 to Symfony 4.4: If you are making direct use of any Symfony code, you may need to update that code to its Symfony 4.4 equivalent

  • Twig 1 to Twig 2: There are a few changes and deprecations in Twig 2 that may require code updates in your templates. See Preparing for use of Twig 2 in Drupal 9

  • jQuery UI: Most of the world has moved on from jQuery UI, and in Drupal 9, so follows Drupal. Most jQuery UI components are being removed from core, with a few exceptions. If your project requires jQuery UI components, you will need to bring them in as contributed code

There are of course other core dependency changes. See Drupal.org’s Drupal 9 documentation for more information.

Are contributed modules ready for Drupal 9?

The state of contributed modules is much better compared with the transition from Drupal 7 to Drupal 8.  

Most Drupal 8 compatible modules are either fully code compatible with Drupal 9, or require a few deprecated code updates (and a very minor yml file change). The burden on module maintainers is much lighter, and the low level of effort presents an excellent opportunity for the Drupal community to contribute back deprecation changes and test patches.

The transition to Drupal 8 was a tough one, but the architecture and strategy changes that the core development team adopted are paying off in a big way as we look toward Drupal 9 and beyond. The stable, gradual, and predictable advancement of the API creates an environment that promotes best practices, and provides teams with a more predictable landscape with less risk.

Feb 19 2020
Feb 19

One of Drupal’s defining features is its open source nature, and the fact that it invites contributions from its vast community. Having been in the Drupal world for a few years now, I found myself wanting to make my own contributions, but unclear on how best to do so. 

The Drupal docs are sparse in some places, overly verbose in others, and they don’t take into account some of the specific tools we at Phase2 rely on, like Docker and Xdebug.

This guide is intended to help remove all of the barriers for getting patches up to Drupal.org.

Find an issue

If you’re following this guide with an issue already in mind, feel free to skip this section. 

Otherwise, here are some steps to help you find an issue to solve: 

  • Project Work: Any bug, UI problem, error message, or other Drupal issue you run into while working on a project is a good opportunity to contribute
  • Needs Testing: If you know your way around automated testing, then consider searching for the tag ‘needs tests’ in the issue queue. There are often dozens of patches languishing in limbo and waiting for a couple of tests so that they can be finally committed
  • Composer Patches: In all likelihood your project has patches listed in its composer.json that either you, or one of your teammates decided was necessary. This is a perfect place to contribute because you’ll already have some context about what the issue is, which makes it easier to jump in and add whatever is necessary to finally get it committed (which could be as simple as a small test or some documentation)
  • Comment history: Check the post history on your dashboard for issues that you’ve already engaged with. Maybe they could still use some help.
  • Novice Tag: Some issues have been marked with the “novice” tag to indicate that they’re particularly good for people just getting started in the contrib space

Once you find an issue, consider following it by clicking the green star icon. That way you’ll receive notifications on comments. 

Regardless of the approach you use to find an issue, keep in mind that it being either Core or Contrib will have a significant impact on your work. Core patches have a lot more visibility and the potential to help a lot more people, but since they’re core they’re going to have a lot more barriers to entry and will almost certainly require automated testing.

Contrib patches will likely have a smaller impact, but are usually easier to get in because they only need approval from the module’s developers. Once you’ve settled on an issue, you’re ready for the next step. 


Before moving any further I would highly recommend you install Dreditor in your browser. This tool adds a number of tools to Drupal.org, the most important being the “review” button that it will add to patch links.

This button displays patch files in an easy-to-read format that makes full use of javascript and color-coding.


Screenshot of Dreditor
On the left is the default patch display, the right is the same patch appearing in Dreditor. Not only does this make patches from other contributors easier to understand, it also gives a nice UI for commenting on individual lines and code review.

Set up your environment

You should only have to do the set up steps once. If you already have your environment going  feel free to skip this.


Like other projects, we should have a virtual environment to work in. At Phase2 we usually rely on Docker/Docksal for that task. Fortunately this repo makes it easy to set up a D8 environment using Docksal.

  • Clone the repo: 
git clone https://github.com/docksal/drupal8-contrib.git
  • Get to the root:
cd drupal8-contrib
  • Initialize the site:
fin init


Xdebug is a crucial tool for debugging PHP code, but it can be a challenge to set up. Use the following steps to get it running on your Drupal8 local site.

  1. Go into Preferences > Languages and Frameworks > PHP > Debug and make sure your settings match the following screenshot.


Xdebug Settings Screenshot

2. Preferences > Languages and Frameworks > PHP > Servers and make sure your settings match the following screenshot.


Server Settings

3. Go to your docksal.env file and change the value of XDEBUG_ENABLED to 1.


Xdebug Enabled Setting

4. Restart your containers with fin restart to make sure your change to the environment takes effect.

5. If Xdebug still isn’t working, try the following steps:

  • Make sure Xdebug is not muted
  • Make sure your listener (the phone icon in PHPStorm) is enabled
  • Put a breakpoint in index.php, and if Xdebug fires successfully then your code isn’t doing what you think it is
  • Check the docs https://docs.docksal.io/tools/xdebug/

Be aware that Xdebug, while helpful, can also slow down your system a lot, so you don’t always want to have it enabled. Here are two ways to address that issue:

  • Make a Bash alias to toggle xdebug and restart containers fin config set XDEBUG_ENABLED=1 && fin restart
  • Use the existing fin php/xdebug on and fin php/xdebug off commands to toggle xdebug. This shortcut runs faster and doesn’t require restarting containers, but the site will still have some slowdown compared to the previous method.

Git setup

Follow these steps to get Git set up.

  • Are you patching Core or a module?
    • Core
      • cd docroot
      • git checkout -b 12345-short-description\
    • Module
      • cd modules
      • git clone [email protected]:project/machine_name
      • cd module_name`
      • git checkout -b 12345-short-description
  • 12345 is the issue number, and short-description is just something to remind you of the issue.
  • Are you working on top of an existing patch?
    • Make sure you’re in the project’s root directory
    • Download the patch file curl -O https://www.drupal.org/files/[patch-name].patch
    • Apply the patch git apply [patch-name].patch
    • Confirm the changes by running git diff

Code your fix

Now you’re finally able to write the code that will address your issue, and there are a few things to keep in mind while doing so.


First, make sure whatever you add is reinforced by automated tests, either by updating existing tests or making your own. You can run tests using the following commands:

Test an entire file: fin phpunit path/to/file
Run an individual tfin init est: fin phpunit path/to/file --filter=testFunctionName

You also probably want to turn off Xdebug before running tests as it will make them take much longer. 

Functional tests are particularly guilty of this problem and as such can be hard to create. However, if you pass the --printer="\Drupal\Tests\Listeners\HtmlOutputPrinter" tag into your test, your terminal will print out something like the following.


HtmlOutputPrinter Result Screenshot

Each url represents a different step in running the functional test, such as logging in or creating a user. Assuming this test was using the aforementioned docksal setup, you could see the generated html by going to: fin http://drupal8-contrib.docksal/sites/simpletest/browser_output/Drupal_Tests_user_Functional_UserCancelTest-313-38450349.html

Writing good tests will likely save you time in the long run, not only by catching regressions, but also because tested code is far more likely to be merged in.

Commit Strategy

Feel free to follow whatever commit strategy makes sense to you, since the patch file you’ll be creating won’t have an associated git history. That being said, it’s recommended that if you’re working on top of multiple patches that you make a commit between each patch to make jumping between them easier.


Once you’re confident about your work, make sure to add comments where appropriate and run php codesniffer to ensure that your code is up to Drupal 8 standards:
fin composer phpcs path/to/file
 E.g. fin composer phpcs core/modules/field/tests/src/Kernel/FieldStorageCrudTest.php

Once your code is complete, it's time to generate a patch file. Depending on how long you’ve been working on the patch, you’ll probably want to pull down the most recent version of the Drupal master branch and rebase your new branch off of it to make sure that your patch file only contains your changes. The following command will generate your patch file.
git diff master_branchname > [patch_name.patch]
E.g git diff 8.8.x> my_patch.patch

Finally, if you’re adding to an existing patch its best practice to generate an interdiff file to post with your patch. An interdiff is a text file in patch format that makes it easier for reviewers to understand the changes between patches. 
git diff HEAD~1 > ISSUE_ID-COMMENT_ID-interdiff.txt


It should be clear by now that this process requires a lot of detailed and error-prone steps, fortunately there are a number of shortcuts and tools that can make this process easier.


You can put a number of incredibly useful shortcuts into your local gitconfig file.  For example, the following lets you download and apply a patch very easily:

a = apply --index
ac = "!f() { curl [email protected] | git a; }; f"

E.G. git ac https://www.drupal.org/files/issues/2019-04-23/core-field-storage-error-message-3050264-1.patch

This code lets you easily download contrib modules from Drupal.org:

[url "[email protected]:project/"]
 insteadOf = dm:

E.G. (from the modules directory) git clone dm:token

With a little Bash knowledge you can put all kinds of useful shortcuts in the gitconfig file.

Drupal CLI

I also highly recommend installing the Drupal CLI tool which you can find here. There are a host of useful things in this CLI, and here are some of the highlights.

  • List test results for an issue drupalorg drupalci:list [issue_number] 
  • Apply the latest patch from the issue drupalorg issue:apply [issue_number]
  • Generate a patch for the issue from committed local changes drupalorg issue:patch [issue_number]
  • Create a branch for the issue drupalorg issue:branch [issue_number]

Dorgflow also has some helpful command line tools, for instance after all your work is finished you can type dorgflow and it will create a patch and an interdiff file for you.

Make sure you fully understand the process that is being run before using these shortcuts. While they’re helpful, they can break, and you do not want to be fully dependent on them.

Post your patch

Once you’re done with your patch and interdiff, post it to the issue with a comment about what you changed and set its status to ‘needs review,’ this will run all tests and tell other developers that your work is ready for feedback. If your patch isn’t getting a lot of attention, feel free to reach out to people on the official Drupal Slack Channel and ask for reviews. Also, keep in mind that while writing your own patches is great, contribution also involves commenting on and reviewing other people’s work. In general, the more you review, the more likely you are to get your work reviewed.

Jan 30 2020
Jan 30

It all started as a simple question to our Drupal developers:

"If you have a Published node, then create a new Draft, what version do you get from Node::load?"

There were two answers:

  1. "Node::load will always give you the Published revision of the node."
  2. "Node::load will give you the latest revision of the node."

Take a moment to think about this and select an answer before you continue reading.

Plain Drupal 8 without any Content Moderation

When you install a fresh Drupal 8 site, content moderation is disabled. In this situation, life is very simple:

2. Node::load will give you the latest revision of the node

Regardless of whether it is published or not. Going to the node/view page will show the latest revision (and will have a pink background in the standard Bartik theme if it isn't published).  The node/edit page will show the latest revision.

Basically, in this scenario, it doesn't matter whether the node is published or not. The latest revision is always the "default" revision.

Adding Content Moderation

Content Moderation was added to Drupal core in version 8.5.0. When you enable the content_moderation, a basic "Draft, Published, Archived" workflow is created for you.

Using the default workflow, enable it for one of your content types (such as Articles).  Then create an Article and set the workflow state to Published. Then edit the same article and set the workflow state back to Draft.  In this case, the answer is:

1. Node::load will give you the Published revision of the node (not the latest draft).

Now Publish the article again, and then edit the article and set the workflow state to Archived.  Now:

2. Node::load will give you the latest revision of the node (the Archived version, which is not published).

So how does this all work? Why does it seem, at first glance, to be inconsistent?

Understanding the Default workflow state

If you look at the default workflow configuration and edit a State, you will see a checkbox called "Default revision".  In the Drupal node_revision database table, there is a column for each revision of a node called revision_default.

When you save a new revision, Drupal will set the database revision_default column based on the "Default revision" checkbox for the state that you selected.

In the case of the default workflow installed by content_moderation, the Published state has "Default revision" checked (because all Published states must also be set to default). But the Archived state also has "Default revision" checked! Only the Draft state has the "Default revision" unchecked.

The Node::load function, and the node/view page will always show the latest "default" revision of a node.  The node/edit page will always show the latest revision (highest revision id) regardless of the "default" value.  When the highest revision id does not have the revision_default value set to True, Drupal will add a "Latest  Version" tab next to View and Edit that allows you to view the latest revision rather than the current default/published version.

This behavior holds true even at the database layer. The node and node_field_data tables always contain the data for the default revision, not necessarily the published revision.

In code, you can get the latest (highest) revision id for content entities via:

$vid = \Drupal::entityTypeManager()

and then load that revision via:

$node = \Drupal::entityTypeManager()

Custom Workflow States

When creating your own Workflow and States, be sure to understand how this "Default Revision" option works in Drupal 8. The "Published" checkbox for the State is controlling the status property of the node, and the "Default Revision" checkbox is controlling the revision_default property. Any number of states can be set to be Published or Default.

If you are creating an API or other backend code that needs to load either the published revision of a node, or the latest revision (to be edited), be sure you are not just calling Node::load without understanding the default revision. When an API makes changes to content, it should always be loading the latest revision, not just using Node::load.

For example, if you add a state called "Revised" to indicate a node that was previously published and now has a forward "draft" revision, you don't want this marked as a "Default Revision" otherwise users might see unpublished content (or get an "access denied" error if they don't have permission for unpublished content).


In the future it might be nice to have a direct getStorage()->loadLatestRevision() API in Drupal code to make this a bit easier. The storage query has a latestRevision tag method that can be added (for example, creating a loadProperties helper that returns the latest revision of the matching query). But for now, the "default" terminology remains somewhat confusing. As more sites start using the core Content Moderation function I expect to see this continue to improve in core.

Sep 25 2019
Sep 25

Yesterday the digital experience world and the Drupal community received the long awaited answer to the question: What’s going to happen with Acquia? When it was announced, first on Bloomberg that Vista Equity Partners would be buying a majority stake in Acquia which it values at $1B. 

Many were caught off guard by the timing, but an event like this had been expected for a long time. After receiving nine rounds of venture funding totaling $173.5M, it was time. As the leader and largest company in the Drupal space, Acquia has a center of gravity that leaves many asking a new question: What Now for Drupal?

What Are the Angles?

Before I attempt to answer what I think this means for Drupal and the Drupal community, I think it is worthwhile to at least speculate on the strategy Acquia plans to pursue as a part of Vista. It seems that everyone I have heard from both offline and online since the announcement yesterday are speculating on the Vista angle (i.e. why did they want Acquia?). As TechCrunch led with “Vista Equity Partners...likes to purchase undervalued tech companies and turn them around for a hefty profit…” Well that’s pretty much what a PE firm does. And to me less interesting than asking: What does Acquia want from Vista?

What I believe Acquia wanted to get out of this is a heavy weight partner with capital and connections that could help develop Acquia into a more formidable competitor to Adobe, Sitecore and other digital experience platforms (“DXP”). It was just last week that Salesforce Ventures made a very sizeable $300M investment in Automattic, the parent company of WordPress. Things are heating up with all of the top digital experience platforms and no one is going to survive, let alone stay in the front of the pack, without some serious capital behind them. 

Who Wins?

I believe Acquia plans to use Vista’s investment and resources to continue making targeted acquisitions and investments to become a more robust and powerful digital experience platform. I would expect them to grow their suite of products, invest even more heavily in sales and marketing to increase revenue and grow its installed base of customers. 

Vista will then have a more valuable asset from which to pursue either an IPO or a strategic acquisition. It is possible this will follow the pattern of Marketo, which Vista bought and then sold to Adobe for a $3B profit or Ping, which they recently took public in an IPO

So there are mutual interests being met and a fair valuation that gets the necessary attention - so both parties win. I also think customers win from increased product development, competition, and a more robust ecosystem.

What Does This Mean For Drupal?

I think this is the best of all possible scenarios for both Drupal (the product) and the Drupal community. While many will bemoan the intrusion of a large private equity firm into the sacred space of an open source community, change was inevitable and it comes with predictable tradeoffs that have to be measured in the context of a new reality for the space. The community needs the indirect investment that this deal provides and it far outweighs the alternatives. If you assume that there were only a few possible scenarios for Acquia that were going to play out sooner or later, they would be:

  1. Organic growth / status quo - In my opinion, the worst scenario due to the dynamics of the market converging. Without a huge infusion of capital like the Vista deal into Acquia, Drupal simply wouldn’t be able to compete fast enough to stay in the top DXP category against Adobe, Sitecore, Salesforce and WordPress. 

  2. IPO - As a liquidation event for VC investors, this could be perhaps the most lucrative, but the public markets are fickle and I believe that would be very hard on a large open source community and product like Drupal due to the dynamics of control for a public company. This may yet come to pass as the end game for Vista, but I think it is good it was not the immediate play. 

  3. Strategic Acquisition - Salesforce, Amazon, Google, IBM and others of this size would be likely acquirers. Again, this may yet come to pass, but it would not have been an ideal immediate short term play for Drupal because of the weight of influence it would add to the community and open source dynamic.  

  4. PE - Obviously, what did happen. This deal brings the financial strength and strategic opportunities without the messiness of the public markets or a new giant controlling the ecosystem. 

As for the direct benefits to the Drupal project, I take Dries at his word in the personal statement he made on his blog that this strategy will allow Acquia to provide even more for Drupal and the community including: 

  • Sponsor more Drupal and Mautic community events and meetups.
  • Increase the amount of Open Source code [sic] contributed.
  • Fund initiatives to improve diversity in Drupal and Mautic; to enable people from underrepresented groups to contribute, attend community events, and more.

Those are all things that directly benefit the community and make open source Drupal better in addition to the opportunities that the deal affords Acquia to better compete against its rivals. 

How Things Line Up From Here…

Consolidation and funding in the digital experience platform (“DXP”) space are going to make for a wild ride as the top players continue to unveil pieces of their strategy.  

  • Adobe - With Magento and Marketo neatly tucked up, Adobe remains the most competitive player both in terms of market share and the comprehensiveness of the offering, though cost and proprietary lock-in into a single homogenous platform are continued weaknesses. 

  • Acquia / Drupal - Recent acquisitions of platform components like Mautic and Cohesion are likely to continue or increase after the Vista deal in an effort to bring an open and more heterogeneous alternative to bear against the others. 

  • Sitecore - The recent acquisition of a top service provider, Hedgehog followed by the subsequent announcement that Sitecore was laying off 7% of its workforce can’t be interpreted as strong signs of health, but the enterprise market is full of Microsoft ecosystems that will be partial to Sitecore’s underlying technology. 

  • Automattic / WordPress - I have a less insight into the WordPress space than I do Drupal, but the SalesForce Ventures investment doesn’t feel like an attempt to gain a CMS for its own offering (sidenote: Salesforce does have a “CMS” and its Ventures has invested in other CMS’s like Contentful).  Founder Matt Mullenwig told TechCrunch  that Automattic doesn’t want to change course. With the new influx of cash, there won’t be any big departure from the current lineup of products and services. “The roadmap is the same. I just think we might be able to do it in five years instead of 10,” Their recent acquisition of Tumblr is part of a strategy I don’t fully understand, but seems to be a continued volume market move into the larger media space and less about competing with the other platform providers. However, $300M could go a long way in tooling the platform for lots of purposes. 

I also think there is a lot more to watch on the related martech front surrounding customer data. In April, Salesforce and Adobe announced (in the same week) that they were acquiring competing Customer Data Platform (CDP) products. So this is about the whole digital experience stack; where we are likely going to see more acquisitions and consolidation is beyond the CMS. 

What Does This Mean For Our Clients?

Despite the race to create the killer platform, most of our clients have consciously, or organically, adopted heterogenous digital experience platforms. This means they rely on many different components to “weave” together solutions that meets their unique requirements. As Forrester explains, DX is both a platform and a strategy and despite the influence of these major software and cloud players, a “digital experience” needs to be created - that includes strategy, customer research, UX, design, content, brand and the integration of custom and legacy software, and data sources in addition to purchased software. Still, we believe our customers do need to be aware of the changing dynamics in the market and in particular how consolidation will affect their platform investments. 

What Does This Mean For Phase2?

At Phase2, this news comes with much interest. We were one of the very first Acquia partners named after the company was founded in 2008. Over the last 10+ years, we have shared, and continue to share, numerous clients. We are also prolific contributors and implementers in the Drupal space who have been a part of some of the biggest and most impactful Drupal moments over the last ten years. We ourselves, once invested heavily in creating many products that extended and enhanced the capabilities of Drupal because we believe it is a powerful platform for creating digital experiences. 

Over time, as our agency grew and moved “up market”, we have diversified our expertise and have become Salesforce partners, developed Commerce experience, and enhanced our Design, UX and creative capabilities. We also use WordPress, Javascript frameworks for decoupled sites,  and static site generators in conjunction with a wide variety of marketing technologies to create digital experience platforms that go beyond websites and CMS

We will continue to monitor the trends and prepare and enable ourselves to create digital experiences that advance our clients goals and we fully expect Drupal will remain a key component of building those experiences well into the future. 

Jul 23 2019
Jul 23

Here at Phase2, we’re excited to participate in the premiere Drupal government event, Drupal GovCon, held at the National Institutes of Health campus in Bethesda, Maryland where we'll once again be a platinum sponsor.

We hope you will join us for one of North America’s largest Drupal camps, this week.  

You can find Phase2 at our sponsor booth and all over the session schedule:

Why Migrate To Drupal 8 Now

With Drupal 9’s release impending, there has been a resurgence of chatter around Drupal 6/7 migration. If your government organization is still on Drupal 6 or 7 you will won’t want to miss this session. You can read more about the subject here from our on-site presenters, Tobby Hagler, Director of Engineering, and Felicia Haynes, Vice President of Accounts.

Measuring What Matters: Using Analytics To Inform Content Strategy

Content Strategy is the backbone to any successful digital experience. It’s also rarely considered an analytical process, but it should be! Catch our session to learn more, and in the meantime, read about the top 3 content strategy tips for government ahead of Jason Hamrick’s session.

Accessible Design: Empathy In A World With No Average

Our Accessibility expert, Catharine McNally, has a special treat for her session’s attendees: she’ll be hosting some interactive empathy building exercises, to showcase the impact of accessible design for citizens. This is a unique opportunity for anyone involved in the design, content, and UX of their organization’s/agency’s digital experience.

Personalization & Government: The Odd Couple or The Perfect Match?

Personalization has become the new standard, across industries, for improving customer experience, but how can personalization be leveraged in government? Join our CEO, Jeff Walpole, and Vice President of Business Development, Ben Coit,  to explore use cases and how governments can continue thinking about the next generation of citizen experiences.

Read more about scaling personalization in our recent whitepaper

Jul 08 2019
Jul 08

For the past three years, developers, IT, and marketing professionals have gathered in NYC for “Decoupled Days, a growing but impactful event with influential speakers and change agents from across the tech space, to share and learn about decoupled architecture solutions for some of the world’s leading brands and organizations.

While the topic of decoupled architecture solutions may seem hyper-specific for a two-day conference, there is an aggressively (growing) interest and investment in decoupled architecture strategies across industries, all to enable the digital brand experiences they require to stay competitive.

Decoupling the front end of an organization’s platform from their back end allows for more flexible design capabilities and the ability to update the design much faster at the speed that marketing teams need to move. When the back end of your website is decoupled, you can easily integrate and swap micro-services in and out to provide the latest and most useful tools and functionality without a complete site redesign.  

Here at Phase2, we work with many of our enterprise clients on decoupling their digital architectures to meet their brand experience goals. We’re thrilled to participate in the organization, sponsorship, and thought leadership of this event. If you plan to attend this year’s event, (and we think you should!), be sure to check out some of Phase2’s front-end leaders as they share best practices and cutting-edge tooling in the following sessions:

We hope you’ll join us July 17th-18th in NYC to learn more about business, technology, and diversity when it comes to decoupled architectures for seamless omnichannel brand experiences that inspire audiences.

Jun 05 2019
Jun 05

The latest version of Drupal is version 8.7.2. You’re familiar of course. In fact, you’ve been on pins and needles ever since version 8.7.1(a). I’m sure you’ve been instasnaptweeting ever since it was issued.

OK, back to reality: You’re definitely more concerned with getting the data you need, driving great brand interactions, and maintaining costs (and keeping your InfoSec or IT teams happy)—than the latest CMS version.

However, what if I told you that your outdated Drupal 6 or 7 instance could actually hinder you from innovating, drain your budget, and potentially cause unnecessary security risks? And that once you get to Drupal 8, migration costs, security updates, and best-of-breed functionality (like content management and media libraries) from core Drupal contributions will help you deliver the audience experiences that grow your business.

So, should you migrate to Drupal 8 immediately? Yes.

Here’s why:

  • More languages? Not a problem: multilingual capabilities - It doesn’t take an engineering background to know that creating a multilingual site is not an easy task. Taking a step back, there are so many questions to consider:

    • Should content be displayed in a single native language?

    • What percentage of your site traffic is in English vs. other languages?

    • How should you handle media files?

    • Is there a risk if changes are incurred by translations?

    • Will the translations be handled properly? Who will handle translations?

The good news is that creating a multilingual site became a lot easier with Drupal 8 with benefits for both site admins and end users. Drupal 8 is now part of “core” (the basic features). In previous versions of Drupal, you needed to install extra modules (collection of files that contain some functionality)  just to support multilingual, which meant a lot more work, added costs and additional maintenance. Drupal 8 brings multilingual support to core with 4 modules, localized content (which makes usability and translations easier so that content translation can be possible for all types, including taxonomies, field types et al) and there are 100+ default languages included.

  • Ease of content editing - Building layouts has never been more intuitive than with Layout Builder! The tl;dr for Layout Builder allows fielded content on the back-end, but a true drag and drop front-end editing experience. There are even more added benefits including a flexible, admin interface, use of contextual links while keeping structured data. And you can now create layouts for templated content with Layout Builder. Many of Drupal’s competitors don’t allow such a templated approach to be done in browser.  (P.S. Learn more on this fantastic page-editing experience from my colleague, Caroline Casals here.)

  • Responsiveness for all - Responsive behavior is no longer a nice-to-have, but de rigueur.  This was an add-on in Drupal 7, but a built-in feature with Drupal core includes built-in themes that are mobile responsive. Additional web services now allow for content to be accessible from Alexa, Siri, and other virtual assistants.

  • Speed matters! - Drupal 8 has features that will make your websites faster without the need for a lot of technical experience. “Cache tags” are used making caching more efficient and also page loads faster.

  • More robust security - Drupal 8 is the stable version of Drupal; functionalities and major modules have been ported over and are now supported by the Drupal open-source community.

  • Integration friendly - Drupal 8 is set-up so that site administrators can easily use APIs to connect to a number of digital tools. Think: your deep integrations, like web analytic platforms, customer relationship management (CRM), email campaigns, social networks and marketing automation systems; you can rest easier that they’ll perform and communicate in concert.

So why else should you invest now? Drupal 7 is currently on life support.  As of November 2021, Drupal 7 will reach it’s “end of life” (EOL) which means that the version will no longer be supported by core maintainers.  

“No longer being supported” also means the following:

Drupal 9 is scheduled to be released next year (2020) which gives companies about a year to upgrade to Drupal 8. Don’t panic, but develop a reasonable plan for when you'll stop investing in your Drupal 7 platform, keep your site as up-to-date as you can (this will help security), and get on Drupal 8 as fast as you can. Still have questions? Give me a shout.

Jun 04 2019
Jun 04

Historically, migrating your content management system (CMS) or content platform from one major version of Drupal to the next was nothing short of a Herculean task.

Every new version of Drupal meant rebuilding existing functionality, converting (or migrating) your content, and accepting significant changes along the way. Because of this, it’s become commonplace to see stakeholders want to leapfrog Drupal versions (e.g., 5 to 7, or 6 to 8), to extend the life of both their old and new platforms for as long as possible for the least amount of transitional pain.

But this is no longer the case.

If you’ve been swimming in Lake Drupal for the last year, you’ve already heard the clarion call to prepare for Drupal 9, due to release in June of 2020. Most of the messaging has been tailored towards planning ahead for your migration, regardless of what version your CMS sits on today.

All too often, many of those plans call for waiting until Drupal 9 is ready before migrating away from your older platform… you know, to leapfrog ahead and save yourself the pain of CMS migration more frequently than you need.

And that’s only half the story.

If you’re not already on Drupal 8, the time to migrate is now. Drupal 9’s release within a year marks the end of life for Drupal 7. You could be incurring unnecessary security risks, all manner of technical debt, and a deluge of growing time-to-cost barriers — all while missing out on the most useful Drupal 8 features for marketers and developers like Layout Builder, an extensible media management system, and editorial workflow. Most importantly, Drupal 8 is essentially Drupal 9, so you’ll be ready for that upgrade when the time comes without another major effort.

Read: When the time comes, your Drupal 8 site can become Drupal 9 without migrating again.

TL;DR: Migrate Out of 6 or 7 Before 9 Drops

If you already know you need to migrate from Drupal 6 or Drupal 7, but you’re not sure where to begin, read this primer to help you scope, identify, and take steps to start the process.

If you’re still not convinced why you should already be migrating, here’s why it’s critical to begin migration ASAP.

If you’re on 6

  • Drupal 6 support has already ended (as of February 2016). If you’re still on this version, you’ve got unmitigated security risks that, if left unchecked, could cost your organization unforeseen resources or damages.

  • Migration from Drupal 6 will not be part of Drupal 9 core. It will be deprecated in Drupal 8.8 and removed in Drupal 9 to become a contrib module.

  • Core migration maintainers will manage the contrib module and support it for a while, but it will eventually become community owned. That leaves the future direction of that module unclear.

If you’re on 7

  • Support ends in 2021 (and that’s not too far off!). If you haven’t planned or budgeted for this, it’s time to start now, especially if your budget planning is a traditional calendar year

  • You might have painful memories of past Drupal migration. While previous versions were, in fact, incompatible across migrations, this is not the case from Drupal 7 to Drupal 8

  • This is the “last great migration.” Migrating from Drupal 8 to future versions (Drupal 9 and beyond) won’t require a similar migration effort. Upgrading from Drupal 8 to Drupal 9 will be more or less a drop-in-replacement as a version bump. Relatively speaking, there will be significantly less effort in upgrading from Drupal 8.9 to Drupal 9.0 than in previous core versions.

  • You don’t want to spend the next 12-16 months on an old system that you’re already wanting to upgrade. Just rip the band-aid off now. Drupal 9 is essentially a version of Drupal 8 — so the wait is needless, and will only be more complex and costly when making a two-version jump.

  • When Drupal 7 support is officially dropped, security updates will only be managed by third-party vendors.

[Note for developers: here’s how to navigate some of the more complex aspects of 8 on D7.]

[Note for marketers: even two years ago, we hosted a webinar on the benefits of Drupal 8 for marketers.]

But Drupal 8 Isn’t Drupal 9, Yet

On the day of release, Drupal 9.0 will be the same as Drupal 8.9, but with these significant differences:

  • External dependencies (such as Symfony) will be upgraded to the latest major version.

  • Deprecated code in Drupal 8 will be removed, potentially breaking custom or contrib modules still using that code.

Otherwise, there will be no significant core API breaking changes (as in previous major version upgrades), maintaining all the same core features and functionality. This means that you can effectively upgrade from Drupal 8 to Drupal 9 without overhauling your CMS or migrating content, simply by updating a point release.

Migrating to Drupal 8 now will prepare you for Drupal 9, but you will still need a Drupal 9 readiness plan. Knowing what is being deprecated and how that affects your CMS is essential to your roadmap. However, with a readiness plan in hand, your Drupal 8/9 platform’s lifespan will be significantly increased.

Bottom-line: The thing that you’re waiting for is already here. Don’t cling to an old system that doesn’t meet your needs anymore — and is potentially costing you more time, money, and increasing your security risk. 

Resource Bank:

About Drupal Sun

Drupal Sun is an Evolving Web project. It allows you to:

  • Do full-text search on all the articles in Drupal Planet (thanks to Apache Solr)
  • Facet based on tags, author, or feed
  • Flip through articles quickly (with j/k or arrow keys) to find what you're interested in
  • View the entire article text inline, or in the context of the site where it was created

See the blog post at Evolving Web

Evolving Web