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.18

Tags are ordered alphabetically: alpha < beta < dev < rc. The stable version 4.0.18 is always greater than any 4.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

TagWhen to useWho installs it
devActive development, things may breakOnly you and your CI
alphaFirst round of internal testingInternal team
betaFeature-complete, looking for bugsAdventurous early adopters
rcRelease candidate, should be stableAnyone 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      ahead

Pre-release versions are publishable to Hex but won't be installed by default by users who have ~> version constraints.