npm Versioning & Publishing
Semantic versioning, version ranges, and the publish workflow are essential knowledge for anyone maintaining a library or CLI tool. Understanding them also helps you manage dependencies responsibly in applications.
Semantic Versioning (semver)
# semver format: MAJOR.MINOR.PATCH[-PRERELEASE][+BUILD]
# 1.4.2-beta.1+build.123
# MAJOR: breaking (incompatible API changes)
# MINOR: new features (backward compatible)
# PATCH: bug fixes (backward compatible)
# Version ranges in package.json:
# Exact: "1.4.2" - only this version
# Caret: "^1.4.2" - >=1.4.2 <2.0.0 (same major; allows minor+patch)
# Tilde: "~1.4.2" - >=1.4.2 <1.5.0 (same minor; allows patch only)
# Range: ">=1.4.0 <2.0.0"
# Latest: "*" or "" - any version (dangerous)
# Any 1.x: "1.x" or "1.*"
# Special cases:
# "^0.4.2" → >=0.4.2 <0.5.0 (0.x.x: ^ only allows patch changes!)
# "^0.0.3" → >=0.0.3 <0.0.4 (0.0.x: ^ pins exactly)
# Reason: before 1.0, minor versions may have breaking changes
# Prerelease versions (not matched by ^ unless explicitly specified)
# alpha < beta < rc < stable
# 1.0.0-alpha.1 < 1.0.0-alpha.2 < 1.0.0-beta.1 < 1.0.0-rc.1 < 1.0.0
# Check what a range resolves to
npx semver -r "^1.4.2" 1.4.3 1.5.0 2.0.0 # Tests versions against range
# Lock file pins exact versions regardless of ranges
# package.json: "^4.18.2" → package-lock.json: "4.18.3" (exact)Bumping Versions
# npm version: bumps package.json, creates git commit + tag automatically
npm version patch # 1.4.2 → 1.4.3 (bug fix)
npm version minor # 1.4.2 → 1.5.0 (new feature)
npm version major # 1.4.2 → 2.0.0 (breaking change)
# Prerelease increments
npm version prerelease --preid=alpha # 1.4.2 → 1.4.3-alpha.0
npm version prerelease # 1.4.3-alpha.0 → 1.4.3-alpha.1
npm version prepatch # 1.4.2 → 1.4.3-0
npm version preminor # 1.4.2 → 1.5.0-0
npm version premajor # 1.4.2 → 2.0.0-0
# Set specific version
npm version 2.0.0-rc.1
# Skip git tag
npm version patch --no-git-tag-version
# Custom commit message
npm version patch -m "chore: release v%s"
# After version bump, push commit AND tag:
git push && git push --tagsPublishing to npm Registry
# Login to npm
npm login # Interactive
npm login --scope=@myorg # For scoped packages
npm whoami # Check who you're logged in as
npm token list # List auth tokens
npm token create --read-only # Create CI token
# What gets published?
# Whitelist: "files" field in package.json
# Blacklist: .npmignore (like .gitignore for publishing)
# Default: everything EXCEPT node_modules, .git, tests
# .npmignore example:
# src/
# tests/
# *.test.ts
# tsconfig.json
# .env*
# Preview what would be published (dry run)
npm pack --dry-run # Lists files that would be packed
npm pack # Creates .tgz; inspect it
npm publish --dry-run # Full dry run of publish
# Publish
npm publish # Publish public package
npm publish --access public # Required for scoped packages on first publish
npm publish --access restricted # Scoped package, private (requires paid account)
npm publish --tag beta # Publish as beta dist-tag (not "latest")
# Publish prerelease without updating "latest" tag
npm publish --tag next # Users install with npm install pkg@next
# Deprecate a version
npm deprecate my-pkg@1.0.0 "Use 2.x instead"
npm deprecate my-pkg@"<2.0.0" "All 1.x versions are deprecated"
# Unpublish (72-hour window, or request support)
npm unpublish my-pkg@1.0.0
npm unpublish my-pkg --force # Unpublish entire package (careful!)
# dist-tags
npm dist-tag ls my-pkg # List all dist-tags
npm dist-tag add my-pkg@2.0.0 latest
npm dist-tag add my-pkg@1.0.0 legacy
npm dist-tag rm my-pkg betaScoped Packages & Private Registry
# Scoped packages: @scope/package-name
# Scope can be your username (@alice/utils) or org (@myorg/sdk)
# Free scoped packages are public; private requires paid npm org account
# Install scoped package
npm install @myorg/design-system
npm install @myorg/design-system@2.1.0
# Publish scoped package
npm publish --access public # First publish must set access explicitly
npm publish # Subsequent publishes remember access setting
# Use GitHub Package Registry instead of npmjs.com
# .npmrc in project root:
@myorg:registry=https://npm.pkg.github.com
//npm.pkg.github.com/:_authToken=${NPM_TOKEN}
# Use Verdaccio for self-hosted npm registry
npm install -g verdaccio
verdaccio # Start server on :4873
npm config set registry http://localhost:4873
npm adduser --registry http://localhost:4873
# .npmrc for team (commit to repo, never commit tokens)
# registry=https://registry.npmjs.org/
# @myorg:registry=https://npm.pkg.github.com
# save-exact=true
# engine-strict=trueKeep your own version of these notes — editable, searchable, and organised by your stack.
Start free