Releaser supports the full SemVer 2.0 pre-release lifecycle. This guide explains how tags work, when to use each one, and the rules behind them.
Why pre-release tags?
When you're developing a new feature across multiple packages, you don't want to publish stable versions until everything is ready. Pre-release tags let you:
- Publish test versions to Hex without affecting stable users
- Iterate quickly (
dev.1 → dev.2 → dev.3) without burning version numbers - Promote through stages (
dev → beta → rc → stable) as confidence grows - Keep the same base version through the entire cycle
The lifecycle
Here's a real-world example. You're adding CartaPorte 3.1 support to
cfdi_complementos, currently at 4.0.17:
# Start development — creates first dev pre-release
$ mix releaser.bump cfdi_complementos patch --tag dev
# 4.0.17 → 4.0.18-dev.1
# Fixed a bug in the XSD validation, bump again
$ mix releaser.bump cfdi_complementos patch --tag dev
# 4.0.18-dev.1 → 4.0.18-dev.2
# Another fix
$ mix releaser.bump cfdi_complementos patch --tag dev
# 4.0.18-dev.2 → 4.0.18-dev.3
# Feature is complete, promote to beta for QA
$ mix releaser.bump cfdi_complementos patch --tag beta
# 4.0.18-dev.3 → 4.0.18-beta.1
# Notice: base stays at 4.0.18!
# QA found a bug, fix and bump
$ mix releaser.bump cfdi_complementos patch --tag beta
# 4.0.18-beta.1 → 4.0.18-beta.2
# QA approved, promote to release candidate
$ mix releaser.bump cfdi_complementos patch --tag rc
# 4.0.18-beta.2 → 4.0.18-rc.1
# Everything looks good — release!
$ mix releaser.bump cfdi_complementos release
# 4.0.18-rc.1 → 4.0.18
The entire cycle used a single base version: 4.0.18.
Rules
There are three rules that control how tags behave:
Rule 1: Clean version + tag = bump base + add tag
Starting from a stable version, adding a tag bumps the base first:
$ mix releaser.bump my_app patch --tag dev
# 1.0.0 → 1.0.1-dev.1
# ^^^^^ bumped
$ mix releaser.bump my_app minor --tag dev
# 1.0.0 → 1.1.0-dev.1
# ^^^^^ bumped
This ensures the dev version is ahead of the current stable.
Rule 2: Same tag = increment number only
When you bump with the same tag, only the number increments. The base stays:
$ mix releaser.bump my_app patch --tag dev
# 1.0.1-dev.1 → 1.0.1-dev.2
# ^ only this changes
$ mix releaser.bump my_app patch --tag dev
# 1.0.1-dev.2 → 1.0.1-dev.3
This is for iterating within a stage. You're not done with dev, you're just making more dev fixes.
Rule 3: Different tag = keep base, switch tag
When you change tags (promote), the base version stays the same:
$ mix releaser.bump my_app patch --tag beta
# 1.0.1-dev.3 → 1.0.1-beta.1
# ^^^^^ same ^^^^^^ new tag
$ mix releaser.bump my_app patch --tag rc
# 1.0.1-beta.2 → 1.0.1-rc.1
# ^^^^^ same ^^^^ new tag
This makes sense because promoting from dev to beta doesn't change the code — it just changes the confidence level.
SemVer ordering
Hex respects SemVer 2.0 ordering. Pre-release versions are always less than the stable version with the same number:
4.0.18-alpha.1 < 4.0.18-beta.1 < 4.0.18-dev.1 < 4.0.18-rc.1 < 4.0.18Tags are ordered alphabetically:
alpha < beta < dev < rc. The stable version4.0.18is always greater than any4.0.18-*.
This means users with {:my_app, "~> 4.0"} will not get pre-release
versions unless they explicitly opt in with {:my_app, "~> 4.0.18-dev"}.
Common tags
| Tag | When to use | Who installs it |
|---|---|---|
dev | Active development, things may break | Only you and your CI |
alpha | First round of internal testing | Internal team |
beta | Feature-complete, looking for bugs | Adventurous early adopters |
rc | Release candidate, should be stable | Anyone willing to test |
You can use any string as a tag. These are just conventions.
With cascade
When you bump an app with a tag, cascaded dependents get a plain patch bump (no tag). This is intentional — only the app you're actively developing gets the pre-release tag:
$ mix releaser.bump cfdi_csd patch --tag dev --dry-run
Version changes:
cfdi_csd 4.0.16 → 4.0.17-dev.1 (direct)
cfdi_xml 4.0.18 → 4.0.19 (cascade) ← no tag
sat_auth 1.0.1 → 1.0.2 (cascade) ← no tag
With publishing
mix releaser.status shows pre-release versions as a distinct status:
Package Local Hex Status
cfdi_complementos 4.0.18-dev.3 4.0.17 pre-release
cfdi_xml 4.0.19 4.0.18 aheadPre-release versions are publishable to Hex but won't be installed by
default by users who have ~> version constraints.