โ† Back to DevBytes

Standard Version: Complete Configuration Guide

Introduction to Standard Version

standard-version is a popular npm utility that automates the versioning and changelog generation process for JavaScript projects. It leverages Conventional Commits โ€” a structured commit message format โ€” to determine the next semantic version bump and to produce a clean, human-readable changelog. By integrating standard-version into your workflow, you eliminate the manual overhead of editing package.json, tagging releases, and writing changelogs by hand.

Originally maintained under the standard-version package and now succeeded by community forks like standard-version-exp and release-please, the tool remains a reference implementation for convention-based releases. This guide focuses on the original package's complete configuration model, which applies to most forks as well.

Why It Matters

Installation

Install standard-version as a development dependency in your project:

npm install --save-dev standard-version

Add a convenience script to your package.json:

{
  "scripts": {
    "release": "standard-version"
  }
}

You can now run npm run release to trigger a release. Before doing so, however, you should configure the tool to match your project's conventions.

Conventional Commits Primer

The entire system depends on commit messages following the Conventional Commits specification. The format is:

<type>[optional scope]: <description>

[optional body]

[optional footer(s)]

Common types include:

Example commit messages:

feat(auth): add OAuth2 login flow
fix(parser): handle empty input without crashing
feat(api)!: remove deprecated v1 endpoints

Basic Usage

Once installed, run the release command:

npm run release

This command will:

To preview what would happen without making changes, use the dry-run flag:

npx standard-version --dry-run

Complete Configuration

The real power of standard-version emerges through its configuration options. You can define these in a .versionrc, .versionrc.json, or .versionrc.js file at the project root, or under the "version" key in package.json.

Configuration File Example

Create a .versionrc.json file:

{
  "types": [
    { "type": "feat", "section": "Features" },
    { "type": "fix", "section": "Bug Fixes" },
    { "type": "perf", "section": "Performance Improvements" },
    { "type": "refactor", "section": "Code Refactoring" },
    { "type": "docs", "section": "Documentation", "hidden": false },
    { "type": "test", "hidden": true },
    { "type": "chore", "hidden": true },
    { "type": "style", "hidden": true },
    { "type": "ci", "hidden": true }
  ],
  "skip": {
    "tag": false,
    "commit": false,
    "changelog": false
  },
  "commitAll": false,
  "header": "# Changelog\n\nAll notable changes to this project are documented in this file.",
  "packageFiles": ["package.json"],
  "bumpFiles": ["package.json", "package-lock.json"],
  "tagPrefix": "v",
  "scripts": {
    "prerelease": "npm test",
    "postbump": "npm run build",
    "postchangelog": "echo 'Changelog updated'"
  }
}

Using a JavaScript Configuration

For dynamic configuration, use .versionrc.js:

module.exports = {
  types: [
    { type: 'feat', section: 'โœจ Features' },
    { type: 'fix', section: '๐Ÿ› Bug Fixes' },
    { type: 'perf', section: 'โšก Performance' },
    { type: 'refactor', section: 'โ™ป๏ธ Refactoring' },
    { type: 'docs', section: '๐Ÿ“ Documentation' }
  ],
  skip: {
    tag: process.env.SKIP_TAG === 'true'
  },
  scripts: {
    prerelease: 'npm run lint && npm test'
  }
};

Key Configuration Options Explained

types

The types array controls which commit types appear in the changelog and how they are grouped. Each entry supports:

skip

The skip object lets you bypass specific stages:

{
  "skip": {
    "bump": false,
    "changelog": false,
    "commit": false,
    "tag": false
  }
}

This is useful in CI pipelines where you may want to generate the changelog but handle tagging separately.

bumpFiles

By default, standard-version updates package.json and package-lock.json. You can extend this to other files that contain version strings:

{
  "bumpFiles": [
    "package.json",
    "package-lock.json",
    {
      "filename": "src/version.ts",
      "updater": "standard-version-updater.js"
    }
  ]
}

A custom updater is a Node module exporting an updateVersion function:

// standard-version-updater.js
module.exports.readVersion = function(contents) {
  return contents.match(/VERSION = '(.*)'/)[1];
};

module.exports.writeVersion = function(contents, version) {
  return contents.replace(
    /VERSION = '(.*)'/,
    `VERSION = '${version}'`
  );
};

tagPrefix

Customize the prefix applied to Git tags. The default is v, producing tags like v1.2.3. For monorepos or scoped releases, you might use a different prefix:

{
  "tagPrefix": "release-"
}

scripts

Hook scripts run at specific lifecycle points:

{
  "scripts": {
    "prerelease": "npm run test",
    "posttag": "git push --follow-tags origin main && npm publish"
  }
}

Pre-release and First Release Scenarios

First Release

If your project has no tags yet, force an initial release:

npx standard-version --first-release

This sets the version to 1.0.0 (or whatever is currently in package.json) and generates the initial changelog without bumping.

Pre-release Versions

Generate alpha, beta, or rc releases:

npx standard-version --prerelease alpha
npx standard-version --prerelease beta
npx standard-version --prerelease rc

This produces versions like 1.2.0-alpha.0, 1.2.0-alpha.1, and so on. When you are ready for the stable release, simply run npm run release without the flag, and the pre-release suffix is dropped.

Manual Release Types

Override the automatically detected bump level:

npx standard-version --release-as major
npx standard-version --release-as minor
npx standard-version --release-as patch
npx standard-version --release-as 2.5.0

Customizing the Changelog

Custom Commit Message Format

If your team uses a slightly different commit format, you can adjust the parser:

{
  "commitUrlFormat": "https://github.com/myorg/myrepo/commit/{{hash}}",
  "compareUrlFormat": "https://github.com/myorg/myrepo/compare/{{previousTag}}...{{currentTag}}",
  "issueUrlFormat": "https://jira.example.com/browse/{{id}}"
}

Custom Writer Templates

For advanced changelog formatting, supply a custom writer template using conventional-changelog-writer conventions:

{
  "writerOpts": {
    "transform": function(commit, context) {
      if (!commit.type) {
        return;
      }
      if (commit.scope === '*') {
        commit.scope = '';
      }
      if (typeof commit.hash === 'string') {
        commit.shortHash = commit.hash.substring(0, 7);
      }
      return commit;
    },
    "groupSort": ["Features", "Bug Fixes", "Performance Improvements"]
  }
}

Integrating with CI/CD

A typical CI pipeline runs tests, then performs the release. Here is a GitHub Actions example:

name: Release

on:
  push:
    branches:
      - main

jobs:
  release:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm test
      - name: Configure Git
        run: |
          git config user.name "CI Bot"
          git config user.email "ci-bot@example.com"
      - name: Run standard-version
        run: npx standard-version
      - name: Push tags
        run: git push --follow-tags origin main
        env:
          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

The fetch-depth: 0 setting is critical โ€” standard-version needs the full commit history to analyze changes since the last tag.

Monorepo Considerations

For monorepos, standard-version alone is limited. You typically combine it with tools like lerna or use --tagPrefix to distinguish packages:

{
  "tagPrefix": "@myorg/core@"
}

Alternatively, configure separate .versionrc files per package and run releases individually from each package directory.

Best Practices

Common Pitfalls

Missing Commit History

If standard-version reports no changes, ensure you have committed work after the last tag. Shallow clones in CI also cause this โ€” always use fetch-depth: 0.

Incorrect Version Bumps

If a feat commit only triggers a patch bump, check that your commit messages strictly follow the Conventional Commits format. A missing colon or wrong type keyword causes the parser to treat the commit as a non-release commit.

Changelog Not Updating

Verify that skip.changelog is not set to true in your configuration. Also confirm that CHANGELOG.md is writable and not ignored by Git in a way that prevents commits.

Example: Full Project Setup

Here is a complete package.json snippet combining scripts, commitlint, and standard-version:

{
  "name": "my-awesome-lib",
  "version": "1.0.0",
  "scripts": {
    "test": "jest",
    "lint": "eslint .",
    "release": "standard-version",
    "release:alpha": "standard-version --prerelease alpha",
    "release:dry": "standard-version --dry-run"
  },
  "devDependencies": {
    "@commitlint/cli": "^19.0.0",
    "@commitlint/config-conventional": "^19.0.0",
    "husky": "^9.0.0",
    "standard-version": "^9.5.0"
  },
  "standard-version": {
    "types": [
      { "type": "feat", "section": "Features" },
      { "type": "fix", "section": "Bug Fixes" },
      { "type": "perf", "section": "Performance" },
      { "type": "refactor", "section": "Refactoring" },
      { "type": "docs", "section": "Documentation" },
      { "type": "chore", "hidden": true },
      { "type": "test", "hidden": true }
    ],
    "scripts": {
      "prerelease": "npm run lint && npm test"
    }
  }
}

Pair this with a commitlint.config.js:

module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      ['feat', 'fix', 'perf', 'refactor', 'docs', 'test', 'chore', 'style', 'ci']
    ]
  }
};

And a Husky hook to enforce it:

npx husky add .husky/commit-msg 'npx --no-install commitlint --edit "$1"'

Conclusion

standard-version brings discipline and automation to the release process by tying version bumps and changelog generation directly to your commit history. With a well-crafted .versionrc configuration, enforced Conventional Commits through commitlint, and a CI pipeline that runs releases automatically, your team can ship confidently and consistently. The upfront investment in commit conventions pays off every release cycle โ€” you get accurate changelogs, predictable semantic versions, and a fully auditable release trail without manual bookkeeping. Whether you are maintaining a small library or a large application, mastering standard-version's configuration options gives you a release workflow that scales with your project.

๐Ÿ›  Tools from DevBytes

Inventory Tracker Pro โ€” Excel inventory system, low-stock alerts ยท $19
AI Dev Kit for Mac โ€” local AI dev environment templates ยท $9.99
KeyMapper for Mac โ€” custom keyboard shortcut toolkit ยท $7.99

โ† Back to all articles