If you've ever wondered how tools like semantic-release automatically decide whether your next release is 1.2.3 or 2.0.0, the answer is: your commit messages. Here's how SemVer works and how to automate it on GitHub.
1. What is SemVer?
Semantic Versioning follows the pattern:
MAJOR.MINOR.PATCH
1 . 4 . 2
Part
Bumped when...
Example
MAJOR
You make a breaking change
1.4.2 → 2.0.0
MINOR
You add a new feature (backward-compatible)
1.4.2 → 1.5.0
PATCH
You fix a bug (backward-compatible)
1.4.2 → 1.4.3
The idea: anyone reading your version number can immediately tell if upgrading is safe, adds features, or might break their code.
2. How Commits Map to Version Bumps
Tools like semantic-release don't guess the version — they read your commit messages, following the Conventional Commits format:
<type>(<scope>): <description>
[optional body]
[optional footer]
Commit type
Version bump
fix: correct null pointer on login
PATCH
feat: add dark mode toggle
MINOR
feat!: change auth API response shape OR footer with BREAKING CHANGE:
MAJOR
chore:, docs:, style:, refactor:, test:
No release (unless configured otherwise)
Examples
git commit -m "fix: resolve crash on empty cart"
# → 1.4.2 → 1.4.3
git commit -m "feat: add CSV export to orders page"
# → 1.4.2 → 1.5.0
git commit -m "feat!: remove deprecated /v1/users endpoint
BREAKING CHANGE: /v1/users is removed, use /v2/users instead"
# → 1.4.2 → 2.0.0
The commit-analyzer plugin scans every commit since the last release, finds the highest-impact type, and decides the next version accordingly — no manual version bumping needed.
3. Implementing It on GitHub
Step 1: Install semantic-release
npm install --save-dev semantic-release \
@semantic-release/commit-analyzer \
@semantic-release/release-notes-generator \
@semantic-release/changelog \
@semantic-release/github \
@semantic-release/git
Step 2: Add the release config
Create release.config.js in your project root:
module.exports = {
branches: ["master"],
plugins: [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
"@semantic-release/changelog",
{
changelogFile: "CHANGELOG.md"
}
],
"@semantic-release/github",
[
"@semantic-release/git",
{
assets: ["CHANGELOG.md"],
message:
"chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
]
]
};
What each plugin does
Plugin
Responsibility
commit-analyzer
Reads commit messages since last release, determines bump type (patch/minor/major)
release-notes-generator
Generates human-readable release notes from commits
changelog
Writes/updates CHANGELOG.md with the new release notes
github
Creates a GitHub Release with notes, and can comment on related issues/PRs
git
Commits the updated CHANGELOG.md back to the repo, tagged [skip ci] to avoid an infinite CI loop
branches: ["master"] restricts releases to only run off the master branch — commits merged elsewhere won't trigger a release.
Step 3: Add a GitHub Actions workflow
# .github/workflows/release.ymlname: Release
on:
push:
branches:
- master
jobs:
release:
runs-on: ubuntu-latest
permissions:
contents: write
issues: write
pull-requests: write
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/setup-node@v4
with:
node-version: 20
- run: npm ci
- name: Release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: npx semantic-release
Notes:
fetch-depth: 0— semantic-release needs full commit history to analyze all commits since the last tag.GITHUB_TOKENis auto-provided by GitHub Actions — no extra secret setup needed for basic use.permissions: contents: writeis required so the action can push the version tag, changelog commit, and create the GitHub Release.
Step 4: Commit using conventional commits going forward
git commit -m "fix: handle expired token gracefully"
git push origin master
On push to master, the workflow runs, analyzes commits, bumps the version, updates CHANGELOG.md, tags the release, and publishes it to GitHub Releases — all automatically.
Takeaway
SemVer isn't just a numbering convention — paired with Conventional Commits and semantic-release, it turns your commit history into an automated release pipeline. Write disciplined commit messages (fix:, feat:, feat!:/BREAKING CHANGE:), push to your release branch, and let CI handle versioning, changelogs, and GitHub Releases for you.