Semantic Versioning for WordPress

One of the biggest struggles facing both the maintainers of WordPress and individuals tasked with maintaining WordPress sites is updates. Running updates often gets ignored by non-technical users afraid of breaking things, and the WordPress core team is doing everything they can to mitigate the security risks involved with not updating. If you don’t install updates, you don’t get security patches.

The phased approach to easing the update burden has involved auto-updating minor releases of WordPress core and culminated in WordPress 5.5 giving users the option to enable auto-updates for plugins, themes, core – the whole thing. Further, these auto-updates are automatically enabled for brand new websites. And in many ways, this is a good thing. But it’s also asking for trouble.

Wild Wild West

While WordPress has built a reputation on maintaining backwards compatibility, the same cannot universally be said of the many plugins and themes available. Which is fine! I’m not here to make a value statement on maintaining backwards compatibility. The important thing to note is that the WordPress core team cannot guarantee the integrity of third-party plugin updates.

Which makes the idea of enabling auto-updates for plugins by default pretty scary, if not straight-up irresponsible. Nothing like waking up one day to find functionality on your site broken because updates ran without you having the opportunity to test them (or skip them altogether for fear of breaking things).

But it’s just as bad to wake up one day to find your site hacked because you’ve been putting off those updates. How, then, can we best keep WordPress installs secure while avoiding the risks of auto-updates?

Mitigating risk

Maintaining security isn’t a new problem facing code libraries, so many people have set out to solve it in the past. Developers need the ability to release security updates to users quickly with minimal chance of breaking anything for those users.

And these security updates are just one end of a spectrum of kinds of updates. On the other end of this spectrum are updates that definitely will break things. While WordPress has generally avoided this entirely, other libraries have not. Breaking backwards compatibility often allows developers to make major performance and feature improvements, adhere to modern coding standards, and tackle other issues related to technical debt. Once again, not here to make a value statement on development one way or another. But it does mean developers need a way to communicate to end users that an update is going to break things and they need to do some testing before updating.

The development community came up with a great solution to all of this a long time ago. It’s called semantic versioning.

If you’re already familiar with semantic versioning, skip to here.

A Quick Review of Semantic Versioning

Semantic versioning, or semver, works like this. Every release of a code library gets a version, like so:


In this example, “3” is the major version, “2” is the minor version, and “4” is a point release. Semver says that those positions and the numbering you assign should not be arbitrarily determined. It also says that it isn’t a simple base-ten operation like normal counting (9 becomes 10, 99 becomes 100). It says this (in many more words):

  • The major version should be incremented when introducing a backwards-compatibility breaking change
  • The minor version should be incremented when adding new features that do not break backwards compatibility
  • The point version should be incremented when adding bugfixes or security patches

Semver creates a way in which, at a glance, someone can look at a new version number and know with reasonable certainty how urgent and how safe it is to install that update. Using the above version as an example, you could infer the following about subsequent updates:

  • Version 3.2.5: probably safe, and indeed, probably urgent, to install
  • Version 3.3.0: probably safe to update, though not urgent. Likely includes new features.
  • Version 4.0.0: probably breaks backwards compatibility and should not be installed without testing

And a new major version doesn’t even require that old versions stop getting bugfixes or other maintenance! Perhaps a security patch is released for version 4.0.0, bumping up to 4.0.1. But that security hole also existed in old versions. The developer might even back port the security fix to version 3, resulting in release 3.3.1 and potentially 3.2.6 (if you’re following along). Someone who isn’t ready to jump to major version 4 can still apply security updates their version 3 install (and WordPress actually back-ports security patches very well – note increasingly high point releases the lower you go in this list).

Update Management Outside WordPress

With this hard and fast system in place, it became really easy for non-WordPress developers to start defining their dependencies (like plugins) in single files that computers and automated systems can understand. Here’s an example:

"require": {
    "php": "^7.2",
    "laravel/framework": "^6.18",
    "ramsey/uuid": "^3.0.0",
    "doctrine/dbal": "^2.5",
    "league/fractal": "^0.18",
    "laravel/tinker": "^2.0",
    "fideloper/proxy": "^4.0",
    "spatie/laravel-fractal": "^5.6",
    "spatie/laravel-activitylog": "^3.9",
    "pusher/pusher-php-server": "~4.0",
    "jenssegers/model": "^1.2",
    "guzzlehttp/guzzle": "^6.5"
}Code language: JavaScript (javascript)

Essentially this is a list of “plugins” (we don’t call them that outside WordPress-land, but same idea) with the version at which they should be installed. And notice all those carets (^) and tildes (~). Those tell the system what kinds of updates are safe to essentially “auto-update”. They might mean “only update point releases”, or “only update minor releases”. And all of these explicitly say “Don’t update major versions!” A developer would have to manually increase the major version when they’re ready to test it and ensure they don’t break their app, website, or other tool.

Some people (like Sterner Stuff) use these kinds of files to manage their WordPress installs, too. Recognize some plugin names below?

"require": {
    "php": ">=7.1",
    "composer/installers": "^1.2.0",
    "vlucas/phpdotenv": "^3.4.0",
    "roots/wordpress": ">=5.0.2",
    "roots/wp-config": "1.0.0",
    "oscarotero/env": "^1.2",
    "roots/wp-password-bcrypt": "1.0.0",
    "wpackagist-plugin/aryo-activity-log": ">=1.0.0",
    "wpackagist-plugin/iwp-client": ">=1.3.12",
    "wpackagist-plugin/mailgun": ">=1.4",
    "wpackagist-plugin/redirection": ">=2.4.5",
    "wpackagist-plugin/tiny-compress-images": ">=3.0.0",
    "wpackagist-plugin/wordpress-seo": ">=3.4",
    "wpackagist-plugin/wp-fail2ban": ">=1.0.0"
    // ...
Code language: JavaScript (javascript)

But we won’t get into that today.

Core Resistance

The WordPress core team wants to be able to ensure the most possible WordPress sites get security updates as quickly as possible.

End WordPress users don’t want the risk of installing all updates, automatically, all the time.

Leaving us with this question: which updates are safe to install?

Some folks propose throwing the baby out with the bath water: no auto-updates in WordPress core at all! I don’t agree with this take, and instead, point out that we actually solved this problem a long time ago. With semver.

So shouldn’t core just have adopted semver a long time ago?

The entirety of documented discussion on why WordPress core isn’t using semver can be found in this trac ticket, which also has a link to some Slack chatter about the topic.

Additional prevailing arguments generally revolve around a couple talking points:

  1. WordPress core doesn’t break backwards compatibility, so semver doesn’t make sense
  2. Non-technical end users are scared of versions. Updates should be as hidden and seamless as possible.

Let’s tackle those.

Core Does Break Backwards Compatibility

First, I want to dispel this myth. WordPress 5.0 and the Gutenberg block editor, without question, broke backwards compatibility. It changed the way in which plugin authors like Advanced Custom Fields, CMB2 and others add features to the post edit screen, and didn’t even offer a standardized replacement, leaving these authors to implement their own Javascript, icons and more to keep their features around. If the authors of these plugins hadn’t done anything, WordPress 5.0 would have rendered their plugins completely broken without installing the Classic Editor (I won’t take this time to discuss the politics behind which backwards-compatibility breaking changes are green-lit and which aren’t).

It could also be argued that the block editor broke UI backwards compatibility. A fuzzy interpretation of semver, but for a platform so aimed at non-technical users, I think it carries water.

By either of these lines of argument, a major version increment was due for WordPress with the new editor. And in the core team’s defense, they did increment the major version, but only through clever and difficult planning:

In the current WordPress versioning specification, the left-most digit simply increments in base 10 (just like decimals in regular numbers, like currency). Presumably as a branding effort, the core team intentionally orchestrated the release of the block editor directly after version 4.9 so that it would be assigned 5.0.

But this created issues when a number of other new features were ready months before the new editor was. That meant these new features were essentially blocked by block editor development. The idea of having a version 4.10 was unprecedented and blocked by the potential for issues in existing update code logic. The whole problem could have been avoided if semver were already the accepted norm. Instead, a hard and fast branding decision impeded the release of ready-for-production features related to GDPR and the ServeHappy project.

4.10 is still a major release (i.e. option 2) but allows us to keep the pretty 5.0 number for Gutenberg.


WordPress releases are currently beholden to a counting system that exists for the sake of addition and subtraction, not for software releases. Changing to semver facilitates a release schedule driven purely by development progress and WordPress’s overarching project goals.

If You Never Increment a Major Version, That’s Okay

It’s possible that this is the only (or most notable) instance of WordPress breaking backwards compatibility. Let’s assume it is. That would mean that with semver, today, we’d be at WordPress version 2.6.1 rather than 5.6.1.

Now let’s pretend WordPress actually never broke backwards compatibility. 1.0.0 released way back when, and since then, there have been 39 “branches” of WordPress (that’s how their releases page refers to these). So as of today (February 11th, 2021, shortly after the release of 5.6.1), we’d be sitting at 1.39.1.

And there’s nothing wrong with that. In fact, that’s the point.

Catering to Non-Technical Users

The WordPress core team doesn’t want to teach end users semver, and I can appreciate that. It’s not worth it and causes friction in the update process. They want to encourage users to install updates.

So why are non-technical users seeing version numbers at all? My browser doesn’t ask me to update, and it also doesn’t show me version numbers unless I go looking for them. Everything just happens. There’s no reason WordPress can’t do the same. I’m all for auto-applying security patches, and you could even convince me that auto-updating to new features (minor releases) is totally fine. And all of this could happen without ever showing a WordPress user a version number.

Any automated system trying to accomplish the above, however, needs a versioning system it can understand. Like semver.

WordPress’s commitment to backwards compatibility is about an ethos and should have no bearing on the versioning system. The versioning system is simply a standardized means of communication. The two concerns are totally independent.

So Why Does the Versioning System Matter at All?

You might be inclined to say that if WordPress is “never” going to break backwards compatibility, and should work toward not showing end users version numbers at all, who cares what the versioning system is?

The answer is third-party plugins and themes. WordPress core isn’t the entirety of anyone’s WordPress install. We all have third-party tools we depend on. And those tools do break backwards compatibility – sometimes with reckless abandon!

WooCommerce acknowledged this within their own tools back in 2017 when they released version 3.0.0 and announced a commitment to semver. Pretty cool, since WooCommerce is the single source of income for a variety of businesses that use it. Whether that income is $200 a year or $200,000 a year, there should be as many steps taken as possible to mitigate someone being able to break a site using it.

But come 2020, the WooCommerce team backpedaled. Their primary argument was “we want to do what WordPress core does”. WordPress core is setting precedent for all plugins and themes in its ecosystem.

The team did give a couple other arguments, and being that WooCommerce is an Automattic product, it’s almost certain that these same arguments drive core’s versioning system.

it’s beneficial for merchants if our platform reflects the standards and conventions that WordPress establishes upstream

Stating clearly the influence of core on this decision.

Saddling merchants with the responsibility of understanding the nuances of SemVer, often just to determine whether or not they should update their store’s software, adds unnecessary friction to their user experience.

But this is an ethos and update delivery problem, not a versioning problem. It’s solved by not breaking backwards compatibility ever, not by asking end users to understand semver.

we are adopting WordPress versioning as part of a larger ongoing effort to prioritize backwards compatibility in WooCommerce

And prioritizing backwards compatibility is a noble endeavor! But versioning and prioritizing backwards compatibility are two distinct problems with no bearing on each other.

SemVer provides an easy pathway for introducing backwards incompatible changes by attaching them to a major release

This simply isn’t true. It provides an easy framework for communicating backwards incompatible changes – one that an automated system can understand. It doesn’t tell you when or if to introduce them. It just says how.

Finally, they go on to talk about implicit and explicit communication. Semver is a way to implicitly communicate the contents of a release, while documentation offers explicit communication. To which I say…

There’s no reason you can’t use semver and also maintain thorough documentation and release notes. Laravel does it quite well. And that’s the whole point of a changelog, which WordPress doesn’t even have (unless you count their blog, which is really a marketing tool, not a succinct list of changes that plugin authors like WooCommerce already maintain).

WooCommerce and the Automattic team make a far from convincing argument here.

What Can Be Gained?

Here’s the good stuff: what do we gain by introducing semver into WordPress core and, by setting the example, encouraging plugin authors to follow that lead?

Fast AND Safe Auto-Updates

WordPress’s adherence to maintaining backwards compatibility helps me feel safe hitting that update button each and every time, so I have no qualms encouraging folks who don’t have someone like Sterner Stuff managing their website to enable those auto-updates.

But currently, I would rarely encourage the same for plugins, because folks like WooCommerce have no way to declare to the WordPress auto-update system that a release is not suitable for being upgraded automatically. The WooCommerce team never has to make a release like that if they don’t want to, but not having a system for that in place is dangerous, and encouraging auto-updates without it is irresponsible. Semver is a way for an automated system to understand the nature of updates it’s being asked to install. It does not dictate a release schedule or the nature of those updates.

With that system in place, by encouraging plugin authors to adhere to semver, WordPress could enable point release auto-updates for all plugins and themes by default without ruffling many feathers. Probably even minor releases. Users could have the option to also opt into major auto-updates per plugin, just like today.

No one has to see any version numbers. Just three simple options per plugin:

  • No auto-updates enabled (not recommended)
  • Minor auto-updates enabled (recommended, the default)
  • Major auto-updates enabled (caution: updates may break functionality)

Currently the last one is the default, in line with the WordPress manta “decisions, not options“. But that decision is an irresponsible one, made only because the lack of an intelligent system like semver leaves no other choice.

With semver, the core team accomplishes its goal of getting users to update quickly and without friction, while still creating a system through which plugin authors can ensure their backwards-compatibility breaking changes, if they choose to introduce them, are least likely to be unintentionally installed.


Leave a Reply

Your email address will not be published. Required fields are marked *