We’re planning to introduce a new versioning system for Discourse. Our goal is to provide more choice & predictability for community administrators, while maintaining our development velocity. We’re also adjusting some terminology to align better with other software.
This document will evolve as we receive comments, begin to implement the system, and then expand use of the new release streams.
If you have any comments/suggestions at this stage, please let us know by replying to this topic!
Goals
-
Introduce more regular ‘releases’ for Discourse, which provide a balance between speed-of-development and stability
-
Continue to provide approx. 6-monthly releases which are supported for an extended period
-
Provide overlapping support for regular & extended-support releases, so that administrators have more flexibility over update timing, while continuing to receive critical security updates
-
Keep ceremony around ‘releases’ to a bare minimum. As much as possible should be automated, and should not slow down the core developer experience. ESR releases are just the same as any other release.
-
Naming & procedure should match industry standards, so that it’s easier to explain to developers & end-users
High-level Overview
-
Cut approximately one release per month. The ‘major’ version is the current year, and the ‘minor’ version increments with each release. The patch version number will be incremented for any backported fixes
e.g. the first release of 2026 would be
v2026.0
, next would bev2026.1
, etc.Releases will receive critical fixes for two full release cycles. e.g. support for
2026.0
would continue until2026.2
is released. -
Approximately every 6 months, declare one of those releases as an Extended Support Release (ESR). ESR versions remain supported for 2 releases after the next ESR is declared.
e.g. if v2026.0 is ESR, and v2026.6 is the next ESR, then v2026.0 support will end when v2026.8 is released. Assuming a monthly cadence, that would be a 2 month overlap in ESR support.
-
Provide critical fixes for
latest
, the most recent release, the previous release, and any active ESR versions. -
Rename the
tests-passed
branch tolatest
Example graph of support periods over a year:
gantt
title Discourse Releases and Support Periods (Jan 2026 – Jan 2027)
dateFormat YYYY-MM-DD
axisFormat %b %Y
2026.0 (ESR) :active, 2026-01-27, 2026-09-29
2026.1 :done, 2026-02-24, 2026-04-28
2026.2 :done, 2026-03-31, 2026-05-26
2026.3 :done, 2026-04-28, 2026-06-30
2026.4 :done, 2026-05-26, 2026-07-28
2026.5 :done, 2026-06-30, 2026-08-25
2026.6 (ESR) :active, 2026-07-28, 2027-01-26
2026.7 :done, 2026-08-25, 2026-10-27
2026.8 :done, 2026-09-29, 2026-11-24
2026.9 :done, 2026-10-27, 2026-12-29
2026.10 :done, 2026-11-24, 2027-01-26
2026.11 :done, 2026-12-29, 2027-01-26
Implementation
-
Each release will have a branch cut from
latest
. These will be namespaced, and preserved indefinitely. For example,v2026.1
would have a branch namedrelease/2026.1
-
Each patch release will be tagged. e.g.
v2026.1.0
,v2026.1.1
, etc. -
The latest release will be tagged
release
. The latest ESR will be taggedesr
. -
Previous release will be tagged
release-previous
. Previous active ESR (if any) will be taggedesr-previous
. -
For backwards compatibility, tags matching the existing release streams will be aliased to the closest new equivalent.
stable
→esr
.beta
→release
.tests-passed
→latest
.These will be considered deprecated, and we’ll aim to drop some or all of them in the future. In particular, ‘beta’ is problematic because it gives the impression that Discourse is not production-ready.
-
On
latest
, the version number will be the currently-in-development version, suffixed with-latest
. e.g.2026.3.0-latest
Automated Release Process
Each month, a GitHub action will open a new PR containing a single commit which bumps the version.rb
on main
to the next -latest
version.
Once a human has merged the PR, another GitHub action will detect main
moving to the next -latest
and cut a branch for the completed release. Essentially, this branch becomes a ‘release candidate’. Another automated PR will be opened against the release branch with an update to remove the -latest
suffix from version.rb
, and thereby ‘release’ it.
Usually, we’ll merge these two PRs in quick succession. But having separate PRs for creating and finalizing the release gives us the option to address any issues in the branch prior to finalizing.
%%{init: { 'logLevel': 'debug', 'gitGraph': {'showBranches': true, 'showCommitLabel':true,'mainBranchOrder': 2}} }%%
gitGraph
checkout main
commit id:'version v2026.1-latest'
commit id:'...'
commit id:'....'
branch 'release/2026.1'
commit id:'version 2026.1'
checkout 'main'
commit id:'version v2026.2-latest'
Separately, another GitHub actions workflow will watch for any backported commits to release branches. When any are found, a new PR will be generated to bump the patch version on that branch. A human can decide when to merge those PRs.
All these automations will automatically keep various tags (release
, release-previous
, esr
, esr-previous
, plus backwards-compatibility aliases) up-to-date.
Security Fixes
Security fix workflow remains largely the same, except that we now need to make the fixes in two of these three new places:
-
latest
-
esr
-
esr-previous
-
release
-
release-previous
(Remember, per the earlier illustration, it’s only two of the three because esr-previous
is supported, the new esr
is the same as release
or release-previous
, and we drop support for esr-previous
when that’s no longer true).
When introducing a security fix to latest
, a latest-security-fix
tag will automatically be moved to that commit. docker_manager will be updated to monitor that tag and prompt admins to update. This allows us to release and notify about security fixes without needing to expedite a version bump.
Translations
At the moment, the stable
and tests-passed
branches can be translated in CrowdIn, and the results are regularly integrated. In the new system, we initially plan for latest
and release
to be translatable in CrowdIn.
Ideally, by the time release
becomes release-previous
or esr
, translations will have settled. If there is demand for continuous translations of those versions, then that’s something which could be considered in future.
Plugin/Theme Compatibility
Increasing the use of non-latest
streams of Discourse will increase our dependence on the discourse-compatibility
system. So we need some improvements to the compat system.
Instead of using a .discourse-compatibility
file on main
, we could instead support implicit compatibility based on specially-named branches/tags. This should be much easier than juggling commit hashes manually. For example, a plugin could have branches like
d-compat/v2026.1
d-compat/v2026.2
d-compat/v2026.3
main
(used for any Discourse version which doesn’t have its own branch)
When installing a plugin, Discourse can check for a branch matching the current version. If it exists, check that out. Otherwise, check the .discourse-compatibility
file. Otherwise, check out the default branch.
We can create a public GitHub action which runs on each theme/plugin daily, checks for new Discourse releases, and automatically creates these branches. Each theme/plugin could choose to use this auto-pinning action, or they could go for a more ‘floating’ strategy.
discourse.org
hosting
Initially, our hosted offering will continue to run the latest
version of Discourse. In the future, we’ll be exploring options for our enterprise-tier customers to select a ‘release’ version.
Standard install defaults
Initially, the default will remain latest
. Admins will be able to opt-in to the new release stream in the same way that they opt-in to stable at the moment. We may explore easier switches between release streams in future, once the system is more mature.