Semver explained - why is there a caret (^) in my package.json?

So you're installing new modules with the --save option and get your package.json updated as a side effect. Taking a closer look you notice that there's something in front of the version numbers.

1 "dependencies": {
2     "lodash": "^3.9.2"
3 }

You're guessing this is some sort of way to widen the range of accepted versions. You'd like to be clear on this one since it leaves you with that unpleasant feeling of uncertainty. You wouldn't like to receive a call on your day off from your team member that the dependency you added is breaking the build.

Introducing Semantic Versioning

Prefix character (^) has to do with version numbering scheme called Semantic Versioning or semver. Semantic Versioning dictates what kind of changes cause the version number to be incremented. Semver uses three part version number like 3.9.2 and call these three numbers from left to right as the major, minor and patch numbers.

3 . 9 . 2
major minor patch
Table 1. Semantic Versioning uses three-part version number.

The basic contract for the module maintainer making changes is

  • backward incompatible change increments the major number
  • new functionality that is backwards compatible increments the minor number
  • simple bug fix to existing functionality increments the patch number

For any dependency the release 1.0.0 is considered the first stable release and the semver contract does not apply to releases before it.

Let's take lodash version 3.9.2 as a starting point. Major backwards incompatible change to for example how _.filter() works would make the next release 4.0.0. New optional argument to would make the next release 3.10.0. Fixing a bug that wasn't handling certain corner case in _.find() would make the next release 3.9.3.

Not every module follows Semantic Versioning. A module might use three-part version number, but increment them as they like. The safest way is to check the module documentation.

Giving npm permission to install newer version

When executing npm install in a clean project directory, the version that satisfies package.json is installed for each dependency. Instead of specifying in package.json the exact version to be installed, npm allows you to widen the range of accepted versions. You can allow newer patch level version with tilde (~) and newer minor or patch level version with caret (^). The default when using --save is to use caret (^).

Symbol Dependency Versions Changes
caret (^) ^3.9.2 3.*.*
  • backwards compatible new functionality
  • old functionality deprecated, but operational
  • large internal refactor
  • bug fix
tilde (~) ~3.9.2 3.9.*
  • bug fix
Table 2. Contract for Semantic Versioning in package.json. Examples based on version 3.9.2.

Depending on too old version

When deciding whether to allow patch or minor level newer versions, it is important to note that old versions usually don't receive patches. The most common branch that gets a bug fix is the latest stable branch and it is not that often that they are backported for older versions.

Let's say you are using version 1.3.4 of a library with a tilde (~) as your dependency. The library is well maintained and advances quickly. Today the latest version is actually 1.5.1. Then a mission critical bug is discovered that affects every version to date. The bug gets proper treatment and is soon fixed in version 1.5.2. Now it is likely that there will be no tailored bug fix patch release 1.3.5 for your version and you end up not getting that bug fix.

Updating modules with npm

When executing npm install in a clean project directory everything works nicely. The package.json file is evaluated and satisfying versions are installed for each dependency. Things change when you already have node_modules populated. Running npm install will not re-check if there's even newer version available than you already have installed.

There is a dedicated command npm update for checking and installing newer versions satisfying semver pattern in package.json.

1 npm update

Reproducible builds

When you use version number ranges instead of exact versions there is one important consequence. You can't reproduce the build exactly as it went at a later time. The versions that get installed depend on the time you run npm install. Tomorrow it may install different dependencies than it installs today. This is unsatisfactory for situations where you depend on reproducibility. This is the case for production deployments and public releases.

To make things reproducible you need to replace version number ranges with exact versions. Npm provides tool called npm shrinkwrap for doing this.

Configuring npm defaults

The default behavior in npm is to use caret (^) when using --save to update package.json. This default behavior can be configured with

1 npm config set save-prefix '~'

You can also use --save --save-exact to inform that exact match is desired instead of the default save prefix. You can make this latter option default with npm config set save-exact true.


Semantic Versioning Cheatsheet

Learn the difference between caret (^) and tilde (~) in package.json.

Get Cheatsheet

Share article: