⚡ Node.jsDevTools / CI/CD

Semver Validation in Node.js

Validate semantic version strings in Node.js — parse major, minor, and patch components, detect pre-release tags and build metadata, and validate version strings in CI/CD pipelines and package registries.

Also available in Python

1. Semver anatomy

A semantic version follows the format MAJOR.MINOR.PATCH[-pre-release][+build]:

  2   .  1   .  0   -  beta.1  +  build.001
                                build metadata (ignored for ordering)
                    pre-release identifier
               patch (bug fixes)
         minor (new features, backwards-compatible)
   major (breaking changes)
ComponentRule
MAJORNon-negative integer. 0 = initial development, 1+ = public API
MINORNon-negative integer. Reset to 0 when MAJOR increments
PATCHNon-negative integer. Reset to 0 when MINOR increments
Pre-releaseDot-separated alphanumeric identifiers (no leading zeros in numeric parts)
Build metadataDot-separated alphanumeric identifiers — ignored in version precedence

2. API response structure

Endpoint: GET /v0/semver?value=…

{
  "valid": true,
  "version": "2.1.0-beta.1+build.001",
  "major": 2,
  "minor": 1,
  "patch": 0,
  "prerelease": "beta.1",
  "build": "build.001",
  "formatted": "2.1.0-beta.1+build.001",
  "isStable": false
}
  1. Check valid — strict semver 2.0.0 compliance
  2. Use major/minor/patch for version comparison logic
  3. Check isStabletrue when prerelease is null
  4. Use formatted as the canonical string for storage

3. Valid and invalid examples

StringValid?Reason
1.0.0Standard release
0.0.1Initial development
1.0.0-alphaPre-release
1.0.0-alpha.1Numbered pre-release
1.0.0+build.123With build metadata
1.0.0-beta.1+sha.abc123Pre-release + build
v1.0.0Leading v is not semver
1.0Missing patch component
1.0.0.4Extra version component
1.0.0-01Leading zero in numeric pre-release

4. Pre-release and build metadata

const result = await iv.semver('1.0.0-rc.2+sha.abc123')

console.log(result.isStable)   // false
console.log(result.prerelease) // "rc.2"
console.log(result.build)      // "sha.abc123"

// Common pre-release naming conventions:
// alpha  → early testing, breaking changes expected
// beta   → feature-complete, bug fixes ongoing
// rc.N   → release candidate, near-final
// (none) → stable release

// Precedence: 1.0.0-alpha < 1.0.0-alpha.1 < 1.0.0-beta < 1.0.0-rc.1 < 1.0.0
ℹ️Build metadata (+sha.abc123) is ignored when determining version precedence. 1.0.0+build.1 and 1.0.0+build.2 are considered the same version.

5. CI/CD and package registry use cases

import { createClient } from '@isvalid-dev/sdk'

const iv = createClient({ apiKey: process.env.ISVALID_API_KEY! })

// Validate a single semver string
async function validateVersion(version: string) {
  const result = await iv.semver(version)
  if (!result.valid) throw new Error(`Invalid semver: ${version}`)
  return result
}

// Validate package.json version fields
async function validatePackageVersions(pkg: {
  version: string
  dependencies?: Record<string, string>
}) {
  const toValidate: [string, string][] = [['version', pkg.version]]

  // Validate exact dependency versions (skip ranges like ^1.0.0)
  if (pkg.dependencies) {
    for (const [name, ver] of Object.entries(pkg.dependencies)) {
      if (/^\d/.test(ver)) toValidate.push([name, ver])
    }
  }

  const results = await Promise.allSettled(
    toValidate.map(([, ver]) => iv.semver(ver))
  )

  const errors: Record<string, string> = {}
  toValidate.forEach(([name], i) => {
    const r = results[i]
    if (r.status === 'rejected' || (r.status === 'fulfilled' && !r.value.valid)) {
      errors[name] = `Invalid version: ${toValidate[i][1]}`
    }
  })

  return { valid: Object.keys(errors).length === 0, errors }
}

// Check if a release is stable (no pre-release tag)
async function isStableRelease(version: string) {
  const result = await iv.semver(version)
  if (!result.valid) return false
  return result.prerelease === null
}

// Examples
const v = await validateVersion('2.1.0-beta.1')
console.log(v.major)       // 2
console.log(v.minor)       // 1
console.log(v.patch)       // 0
console.log(v.prerelease)  // "beta.1"

const stable = await isStableRelease('1.0.0-rc.1')
console.log(stable)  // false

Validate before publishing to npm

import { readFileSync } from 'fs'
const pkg = JSON.parse(readFileSync('package.json', 'utf8'))

const result = await iv.semver(pkg.version)
if (!result.valid) {
  console.error(`Invalid version in package.json: ${pkg.version}`)
  process.exit(1)
}
if (!result.isStable && process.env.CI_BRANCH === 'main') {
  console.error('Stable branch requires a stable version (no pre-release tag)')
  process.exit(1)
}

6. Edge cases

Stripping the leading 'v'

💡Git tags often use v1.0.0 by convention, but this is not valid semver. Strip the leading v before validating.
const gitTag = 'v2.1.0'
const cleaned = gitTag.replace(/^v/, '')  // "2.1.0"
const result = await iv.semver(cleaned)

npm range specifiers

⚠️Range specifiers like ^1.0.0, ~2.1.0, or >=1.0.0 <2.0.0 are not semver — they are npm range syntax. The API validates strict semver strings only. Strip or expand ranges before validating.

7. Summary checklist

Validate semver format in CI before publishing
Strip leading "v" from git tags before validation
Check isStable for production deployment gates
Parse major/minor/patch for compatibility checks
Store formatted as canonical version string
Do not pass npm range specifiers to the API
Validate package.json version on every build
Run batch validation with Promise.allSettled

See also

Ready to integrate?

Free tier — 1,000 requests/month. No credit card required.

Get your API key →