Skip to main content

Drupal module semantic versioning for Drupal core support

Published on

A large amount of our time during the Drupal 10 readiness effort was around semantic version discussions. Folks were creating new major versions to add Drupal 10 support while dropping Drupal 9 simultaneously. Technically that follows the semantic versioning guidelines but is a horrible user experience. Users must update the module when they upgrade Drupal core to Drupal 10. Ideally, users could update their modules first and then upgrade Drupal core.

This blog post is taken from part of my talk "Lessons learned from helping port the top contrib projects to Drupal 10." It is also inspired by my coworker Jakob Perry's blog post, "Don’t go making major version changes."

Since posting this blog, I have opened an issue to discuss these guidelines and recommendations:

What is semantic versioning?

For anyone unfamiliar with semantic versioning, I will do a brief introduction. You can read the semantic versioning specification here: These two sentences summarize it the best:

We call this system “Semantic Versioning.” Under this scheme, version numbers and the way they change convey meaning about the underlying code and what has been modified from one version to the next.

When you look at a version, you should be able to understand the scope of changes delivered with that release for its public API. The key is public API which is what your end users interact with.

Semantic versions use three numbers in the form of X.Y.Z where:

  • X Major version number, incremented when backward incompatible changes are introduced
  • Y Minor version number, incremented when new backward compatible changes are introduced, or code is deprecated. It may be used to identify significant new features or internal refactoring.
  • Z Patch version number, incremented when backward compatible bug fixes are made.

Recently added support for the semantic versioning of modules. Previously, modules followed a format of 8.x-X.Y. This identified the supported Drupal core and the module's major and minor versions. That's because each major Drupal core version was generally a rewrite for modules. This changed with Drupal 8.

  • 8.x This identifies the major version of Drupal, such as 6.x, 7.x, and lastly 8.x.
  • X This identifies the major version of the module
  • Y This identifies the minor version of the module

Technically the previous format did not support patch releases. But when the legacy version format is parsed for Composer or other semantic versions, it is tread as having a 0 patch release.

Create releases that bridge versions of Drupal.

We encountered several projects which saw introducing Drupal 10 support as a backward incompatible change. This isn't true. You can provide compatibility layers in your module to support multiple major and minor versions of Drupal core. Once you have allowed users to support both major releases of Drupal core, you can release a new major version of your module.

It is also worth noting that three minor versions of Drupal core always provide security support. At the time of writing, until June, that means 9.4.x, 9.5.x, and 10.0.x are under security support. When 10.1.0 is released in June, security support will switch to 10.1.x, 10.0.x, and 9.5.x

What does this look like in practice? Let's imagine a module has a 2.0.x branch that supports Drupal 9.

2.0.x -> drupal/core:^9

The 2.0.x branch has backward compatibility layers for Drupal core 9.0.x to 9.4.x. When Drupal 9.5.0 was released, we decided to stop supporting 9.0.x, 9.1.x, and 9.2.x, and remove all that backward compatibility cruft. I consider this a minor version change. Why? Those versions of Drupal core no longer have security support. Dropping unsupported minor versions of Drupal core should not break your public APIs and only change how they function internally. That brings us to 2.1.x.

2.1.x -> drupal/core:^9.3

Now, let's jump to the release of Drupal 10. Drupal core 9.3.x is no longer supported. We decide to drop support for Drupal 9.3.x and add support for Drupal 10 with backward compatibility layers. This creates a new minor version increment and brings our module to 2.2.x.

2.2.x -> drupal/core:^9.4||^10

Awesome! Now users can upgrade our module and be ready for Drupal 10 when they're ready. However, we've decided that we don't want to keep coding with those backward compatibility layers and take full advantage of PHP 8.1 language features. So we'll create a new major version.

3.0.x -> drupal/core:^10

And that's how we can leverage semantic versioning best when working with Drupal core minor and major versions.

Here's the graphic I used from my slides.

Screenshot of semantic version examples


My guidelines and recommendations for semantic versioning

I've already stated that I believe dropping an unsupported minor version of Drupal core isn't a major version change. Here are my guidelines and recommendations for semantic versioning for Drupal modules. Some folks who are semantic versioning purists will disagree with me. And that is fine. The semantic versioning specification is relative and able to be interpreted as anyone sees fit.

Major versions

  • Deciding to drop the previous major version and all backward compatibility layers
  • Breaking changes from Symfony or other dependency that cannot have backward compatibility support (such as adding types to method signatures.)
  • Impossible dependency resolution (such as supporting Guzzle 6 and 7 at the same time.)

Minor versions

  • Adding support for a new major version of Drupal core while dropping a minor version of Drupal core
  • Adding major new features and want to provide a minor version out of caution for users

Patch versions

  • Adding support for new Drupal core majors! Technically it's a bug fix, when you use add backward compatibility (which you should be!)
  • Other bug fixes
  • Small features

I honestly think the lines of minor and patch versions are blurred. Most module maintainers are not going to follow the same level of bug back porting like Drupal core can provide. Managing multiple active branches is hard. I see creating new minor versions as a courtesy for end user's to refine their constraints against.

What about supporting PHP versions?

Dropping a PHP version is a breaking change. If you drop PHP 7.4 and only support PHP 8.0 that is a breaking change. However, supporting new version of PHP is not a breaking change. You may provide support for new minor and major releases of PHP without creating backward incompatible changes. Adopting a new version of PHP does not require you to use all of its features. Follow the same guidelines similar to Drupal core support and semantic versioning.