Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Conventional Commits and Semantic Versioning

By Kokil Thapa | Last reviewed: September 2026

Your git log is full of messages like "fix stuff" and "updates." That noise makes every release a guessing game. Conventional Commits and Semantic Versioning fix that by tying each commit to a machine-readable type and mapping those types to MAJOR, MINOR, and PATCH bumps. On production Laravel apps I maintain with Git hooks and CI pipelines, this pairing cuts release friction and keeps changelogs honest. This guide shows the format, the bump rules, and how to wire it into real PHP workflows in 2026.

What are Conventional Commits and Semantic Versioning?

They solve two related problems. Conventional Commits standardise how you write commit messages. Semantic Versioning (SemVer) standardises how version numbers change when behaviour changes.

A conventional commit looks like this:

feat(checkout): add Khalti payment callback handler

BREAKING CHANGE: payment status endpoint now returns JSON instead of plain text

SemVer expresses compatibility as MAJOR.MINOR.PATCH — for example 2.4.1. A breaking change bumps MAJOR. A backward-compatible feature bumps MINOR. A backward-compatible fix bumps PATCH.

The official specs live at conventionalcommits.org and semver.org. Treat them as the source of truth when your team debates edge cases.

Conventional Commits to SemVerGit committype(scope): msgParsercommitlint / CIBump rulefeat / fix / BREAKINGSemVer tagMAJORMINOR · PATCHCHANGELOGgrouped by typeDeploy tagGitLab CI / Deployer
Conventional Commits and Semantic Versioning pipeline: structured commits drive version bumps, changelogs, and release tags.

You do not need fancy tooling on day one. A team that agrees on the format and reviews PR titles consistently already gains most of the benefit. Automation comes next.

How do you write a valid Conventional Commit message?

The format is strict but small. Every commit has a type, an optional scope, a short description, an optional body, and optional footers.

Header structure

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Common types and their typical SemVer effect:

  • feat — new user-facing behaviour → MINOR bump
  • fix — bug fix, no API break → PATCH bump
  • docs, style, test, chore, ci, build, refactor — usually no version bump unless they change runtime behaviour
  • perf — performance fix; many teams treat it as PATCH
  • BREAKING CHANGE footer or ! after type → MAJOR bump

Examples that pass review

fix(auth): reject expired Sanctum tokens on API routes

feat(cart): support NPR and USD display on checkout page

refactor(orders)!: rename OrderService methods for clarity

BREAKING CHANGE: getTotal() is now calculateTotal()

Keep the subject line under 72 characters. Use imperative mood: "add handler," not "added handler." Match what you already expect from good git hygiene practices.

Scopes that help Laravel teams

Scopes should mirror modules your team owns: auth, billing, api, queue, deploy. On a legal-tech portal, scopes like documents or booking make changelogs readable for non-developers.

Commit Message Anatomyfeat(checkout):add Khalti callbackBody: explain why the changewas needed and side effectsRefs: #482BREAKING CHANGE: API response shape
Conventional Commits split into header (type, scope, description), optional body, and footers including breaking-change markers.

How does Semantic Versioning decide MAJOR, MINOR, or PATCH?

SemVer is a contract with downstream consumers. Version 1.4.2 means: same MAJOR → compatible; higher MINOR → new features, still compatible; higher PATCH → fixes only.

Map conventional types to bumps like this:

  1. MAJOR — any breaking change to public API, database schema consumers rely on, or config keys external tools read
  2. MINOR — new backward-compatible functionality
  3. PATCH — backward-compatible bug fixes

Pre-release labels (1.0.0-beta.1) and build metadata (1.0.0+20260910) are valid SemVer. Most PHP apps ship simple three-part numbers unless you publish Composer packages.

Commit signalSemVer bumpExampleTypical consumer impact
BREAKING CHANGE or type!MAJOR1.4.2 → 2.0.0Must update integration code
feat:MINOR1.4.2 → 1.5.0New optional endpoints or fields
fix: / perf:PATCH1.4.2 → 1.4.3Same API, corrected behaviour
docs:, chore:, ci:None (usually)1.4.2 → 1.4.2No runtime change

Reset PATCH and MINOR to zero when you bump a higher segment. After 1.4.9, a feature release becomes 1.5.0, not 1.5.9.

SemVer Bump DecisionNew commit mergedBreaking?BREAKING CHANGEfeat?new featurefix?bug fixMAJOR +1MINOR +1PATCH +1chore/docs/ci → usually no bump
Semantic Versioning bump decision tree mapped from Conventional Commit types and breaking-change footers.

How do you automate Conventional Commits and Semantic Versioning in CI?

Manual versioning fails when releases get rushed. Automation enforces the contract. A typical stack for PHP/Laravel projects includes commitlint, a release tool, and CI gates.

Enforce messages locally and in CI

Install commitlint with the conventional config:

npm install --save-dev @commitlint/cli @commitlint/config-conventional

echo "export default { extends: ['@commitlint/config-conventional'] };" \
  > commitlint.config.js

Add a Husky hook or call commitlint from your existing pre-commit workflow. In GitLab CI, fail the pipeline when messages on a merge request do not parse:

validate-commits:
  stage: test
  script:
    - npm ci
    - npx commitlint --from ${CI_MERGE_REQUEST_DIFF_BASE_SHA} --to ${CI_COMMIT_SHA}

This catches bad messages before they hit main.

Generate versions and changelogs

Semantic-release reads commits since the last tag and publishes a new version. For PHP Composer packages, pair it with a composer.json version field update. For deploy-only apps, tagging alone may be enough.

Alternative: use standard-version or release-please if you want explicit control over release PRs. I have used semantic-release on sister sites that share a Deployer 7 pipeline — the tag triggers deploy, and rollback stays a symlink swap.

Wire releases to Laravel and PHP projects

For a Laravel 13 app on PHP 8.3+, store the app version in config rather than hard-coding Blade footers:

// config/app.php
'version' => env('APP_VERSION', '1.0.0'),

Inject at deploy time from the git tag:

export APP_VERSION=$(git describe --tags --always)
php artisan config:cache

Composer 2.10 projects that publish libraries should bump composer.json version in the same commit as the tag. Consumers pin with caret ranges: "vendor/package": "^2.3".

Automated Release PipelinePR mergeconventionalCI testsPHPUnit / PintRelease jobcalc bumpGit tagv2.3.0DeployDeployer 7Artifacts producedCHANGELOG.md · GitHub/GitLab Release notescomposer.json version (packages)
CI/CD flow: Conventional Commits on merge requests drive Semantic Versioning tags, changelogs, and production deploys.

Validate JSON changelogs or release manifests with a JSON formatter before publishing API metadata. For broader pipeline hardening, see AI code review in CI and testing and optimization practices.

What mistakes break Conventional Commits and Semantic Versioning in production?

Teams adopt the format and still ship confusing versions. These failures show up repeatedly on client projects and internal packages.

Squash merges that hide breaking changes

GitHub and GitLab squash merge by default. If the squash title ignores conventional format, your release tool sees one vague message. Fix: enforce PR title format and pass the PR title into the squash commit.

Mixing app versioning with API versioning

A Laravel app at 3.1.0 can expose /api/v1 and /api/v2 simultaneously. App SemVer and URL API version are related but not identical. Document both in your API versioning strategy.

Shipping breaking changes as PATCH

Renaming a public Eloquent resource field breaks mobile clients. That is MAJOR for the API contract even if the commit author wrote fix:. Code review must catch semantic intent, not just syntax.

Never releasing 1.0.0

SemVer before 1.0.0 treats MINOR as potentially breaking. For production business apps — booking portals, eCommerce stores, legal workflows — pick 1.0.0 at go-live and bump from there.

On projects like Adventure Third Pole Trek, clear release notes help operations staff track booking-module changes without reading git. The same discipline applies to client portals with document uploads where compliance questions appear months later.

Monorepo complexity

One repository with multiple deployable packages needs scoped commits and per-package version tags. Tools like release-please handle multi-artifact repos better than a single global tag.

For infrastructure repos, align with patterns from Ansible provisioning playbooks and Linux administration workflows so server and app versions stay traceable.

How should small teams in Nepal adopt this without over-engineering?

Not every SMB site needs semantic-release on day one. A phased rollout keeps cost sensible — often Rs 0 in tooling, mostly discipline.

  1. Week 1: Document allowed types in CONTRIBUTING.md. Require conventional PR titles in code review.
  2. Week 2: Add commitlint to CI on the main branch only.
  3. Week 3: Tag releases manually using SemVer rules until the log is clean.
  4. Week 4: Automate changelog generation and tie tags to Deployer or GitLab deploy jobs.

If you publish a reusable Composer package, invest earlier. If you run a single WooCommerce or WordPress 7.1 site with no external API, PATCH tags on plugin updates may be enough.

Founders comparing build-vs-buy for custom platforms should weigh release discipline as part of custom software development and ongoing support and maintenance. Good versioning reduces emergency calls after deploy.

Enterprise teams with multiple integrations benefit from pairing SemVer with explicit API development standards and enterprise application governance.

Developers maintaining long-lived codebases can cross-check history using git reflog recovery when a bad release tag needs untangling.

Key Takeaways

  • Conventional Commits use type(scope): description plus optional BREAKING CHANGE footers so machines and humans parse intent.
  • Semantic Versioning maps those signals to MAJOR (break), MINOR (feature), and PATCH (fix) with zero-reset rules between segments.
  • Enforce format in CI with commitlint before automating tags, changelogs, or deploy triggers.
  • Squash-merge PR titles must stay conventional or your release history collapses into noise.
  • Separate app version from API URL version — both need documentation for integrators.
  • Start with team agreement and manual tags; add semantic-release once commits are consistently clean.

People Also Ask

Do Conventional Commits require Semantic Versioning?

No. Conventional Commits stand alone as a message convention. SemVer is the versioning scheme most teams pair with them because the commit types map cleanly to bump rules. You can use conventional messages without automated versioning.

What is the difference between feat and fix in Conventional Commits?

feat introduces new capability a user or API consumer can access. fix corrects incorrect behaviour without adding features. Both are usually production-impacting; docs and chore commits typically skip version bumps.

When should you ship a MAJOR version bump?

Ship MAJOR when you break backward compatibility: removed endpoints, renamed response fields, changed authentication requirements, or migrations that force coordinated client updates. Mark it with BREAKING CHANGE: or ! after the type.

Can you use Conventional Commits with Laravel and PHP projects?

Yes. Laravel apps, Composer packages, and WordPress plugins all benefit. Store the running version in config or composer.json, inject it at deploy from git tags, and run PHPUnit plus static analysis in CI before any automated tag promotes to production.

Ship predictable releases with Conventional Commits and Semantic Versioning

Structured commits turn git history into a release contract. SemVer gives integrators a language for compatibility. Together they reduce Friday-night guesswork about what changed and whether it is safe to deploy. Start with PR title rules, add CI validation, then automate tags when the log earns it. If you want help wiring this into a Laravel pipeline, GitLab CI job, or Deployer workflow on your next build, contact us or explore web development services. For related reading, browse the blog, review portfolio releases, or read about how I work with production teams.

Frequently Asked Questions

Conventional Commits standardise git message structure with a type, optional scope, and description so machines can read intent. Semantic Versioning expresses compatibility as MAJOR.MINOR.PATCH. Together they map commit signals to predictable version bumps and honest changelogs from history alone.

No. Conventional Commits work alone as a message convention. SemVer is the pairing most teams choose because feat, fix, and BREAKING CHANGE map cleanly to MINOR, PATCH, and MAJOR bumps.

feat adds new user-facing or API behaviour and usually triggers a MINOR bump. fix corrects wrong behaviour without new features and usually triggers a PATCH bump. Both affect production; docs and chore typically skip version bumps.

Ship MAJOR when backward compatibility breaks: removed endpoints, renamed response fields, changed auth requirements, or database migrations forcing coordinated client updates. Mark with BREAKING CHANGE in a footer or an exclamation mark after the type.

Use type, optional scope, colon, and imperative description under 72 characters, for example feat(cart): support NPR and USD display. Add an optional body and footers. Breaking changes need BREAKING CHANGE: or type! such as refactor(orders)! with a footer explaining what broke.

MAJOR means incompatible public API, schema, or config changes consumers rely on. MINOR adds backward-compatible functionality. PATCH is backward-compatible fixes only. Reset lower segments to zero when bumping higher ones, so 1.4.9 plus a feature becomes 1.5.0, not 1.5.9.

Install commitlint with the conventional config, enforce messages locally via Husky or pre-commit hooks, and fail GitLab CI pipelines when merge-request commits do not parse. Once the log is clean, add semantic-release or release-please to generate tags and changelogs that trigger deploy jobs.

feat maps to MINOR, fix and perf typically map to PATCH, and BREAKING CHANGE or type! maps to MAJOR. Types like docs, style, test, chore, ci, build, and refactor usually produce no bump unless they change runtime behaviour visible to consumers.

Yes. Store the running version in config or composer.json, inject APP_VERSION from git tags at deploy with php artisan config:cache, and run PHPUnit plus static analysis in CI before automated tags promote to production. Composer 2.10 library packages should bump version in the same commit as the tag.

A Laravel app at 3.1.0 can expose /api/v1 and /api/v2 at the same time. App SemVer tracks overall release compatibility; URL API version tracks endpoint contracts. Document both separately so mobile clients and integrators know which contract they depend on.

GitHub and GitLab squash merge by default. If the squash title ignores conventional format, release tools see one vague message and miss breaking changes buried in individual commits. Fix by enforcing conventional PR titles and passing the PR title into the squash commit message.

semantic-release reads commits since the last tag and publishes automatically; I have used it on sister sites with Deployer 7 where the tag triggers deploy. standard-version and release-please suit teams wanting explicit release PRs. Monorepos with multiple packages benefit from release-please over a single global tag.

Tooling cost is often Rs 0, roughly USD 0 — commitlint and CI gates use existing npm and GitLab infrastructure. The real investment is team discipline: documented types, PR title rules, and code review time.

Before 1.0.0, SemVer treats MINOR as potentially breaking. For production business apps such as booking portals, eCommerce stores, or legal workflows, pick 1.0.0 at go-live and bump from there so operations staff and integrators read release notes with predictable meaning.

Week 1 document allowed types in CONTRIBUTING.md and require conventional PR titles. Week 2 add commitlint to CI on main only. Week 3 tag releases manually using SemVer rules. Week 4 automate changelogs and tie tags to Deployer or GitLab deploy jobs. Single WordPress 7.1 or WooCommerce sites with no external API may only need PATCH tags on plugin updates.

Share this article

0 Comments

Leave a comment

Your email is not published. Comments appear once they have been read. Sign in to have your details filled in.

Quick Contact Options
Choose how you want to connect me: