[comments] How do I add comments to package.json for npm install?

I've got a simple package.json file and I want to add a comment. Is there a way to do this, or are there any hacks to make this work?

{
  "name": "My Project",
  "version": "0.0.1",
  "private": true,
  "dependencies": {
    "express": "3.x",
    "mongoose": "3.x"
  },
  "devDependencies" :  {
    "should": "*"
    /* "mocha": "*" not needed as should be globally installed */
  }
}

The example comment above doesn't work as npm breaks. I've also tried // style comments.

This question is related to comments npm

The answer is


My take on the frustration of no comments in JSON. I create new nodes, named for the nodes they refer to, but prefixed with underscores. This is imperfect, but functional.

{
  "name": "myapp",
  "version": "0.1.0",
  "private": true,
  "dependencies": {
    "react": "^16.3.2",
    "react-dom": "^16.3.2",
    "react-scripts": "1.1.4"
  },
  "scripts": {
    "__start": [
        "a note about how the start script works"
    ],
    "start": "react-scripts start",
    "build": "react-scripts build",
    "test": "react-scripts test --env=jsdom",
    "eject": "react-scripts eject"
  },
  "__proxy": [
    "A note about how proxy works",
    "multilines are easy enough to add"
  ],
  "proxy": "http://server.whatever.com:8000"
}

I've been doing this:

{
  ...
  "scripts": {
    "about": "echo 'Say something about this project'",
    "about:clean": "echo 'Say something about the clean script'",
    "clean": "do something",
    "about:build": "echo 'Say something about building it'",
    "build": "do something",
    "about:watch": "echo 'Say something about how watch works'",
    "watch": "do something",
  }
  ...
}

This way, I can both read the "pseudo-comments" in the script itself, and also run something like the following, to see some kind of help in the terminal:

npm run about
npm run about:watch

I ended up with a scripts like that:

  "scripts": {
    "//-1a": "---------------------------------------------------------------",
    "//-1b": "---------------------- from node_modules ----------------------",
    "//-1c": "---------------------------------------------------------------",
    "ng": "ng",
    "prettier": "prettier",
    "tslint": "tslint",
    "//-2a": "---------------------------------------------------------------",
    "//-2b": "--------------------------- backend ---------------------------",
    "//-2c": "---------------------------------------------------------------",
    "back:start": "node backend/index.js",
    "back:start:watch": "nodemon",
    "back:build:prod": "tsc -p backend/tsconfig.json",
    "back:serve:prod": "NODE_ENV=production node backend/dist/main.js",
    "back:lint:check": "tslint -c ./backend/tslint.json './backend/src/**/*.ts'",
    "back:lint:fix": "yarn run back:lint:check --fix",
    "back:check": "yarn run back:lint:check && yarn run back:prettier:check",
    "back:check:fix": "yarn run back:lint:fix; yarn run back:prettier:fix",
    "back:prettier:base-files": "yarn run prettier './backend/**/*.ts'",
    "back:prettier:fix": "yarn run back:prettier:base-files --write",
    "back:prettier:check": "yarn run back:prettier:base-files -l",
    "back:test": "ts-node --project backend/tsconfig.json node_modules/jasmine/bin/jasmine ./backend/**/*spec.ts",
    "back:test:watch": "watch 'yarn run back:test' backend",
    "back:test:coverage": "echo TODO",
    "//-3a": "---------------------------------------------------------------",
    "//-3b": "-------------------------- frontend ---------------------------",
    "//-3c": "---------------------------------------------------------------",
    "front:start": "yarn run ng serve",
    "front:test": "yarn run ng test",
    "front:test:ci": "yarn run front:test --single-run --progress=false",
    "front:e2e": "yarn run ng e2e",
    "front:e2e:ci": "yarn run ng e2e --prod --progress=false",
    "front:build:prod": "yarn run ng build --prod --e=prod --no-sourcemap --build-optimizer",
    "front:lint:check": "yarn run ng lint --type-check",
    "front:lint:fix": "yarn run front:lint:check --fix",
    "front:check": "yarn run front:lint:check && yarn run front:prettier:check",
    "front:check:fix": "yarn run front:lint:fix; yarn run front:prettier:fix",
    "front:prettier:base-files": "yarn run prettier \"./frontend/{e2e,src}/**/*.{scss,ts}\"",
    "front:prettier:fix": "yarn run front:prettier:base-files --write",
    "front:prettier:check": "yarn run front:prettier:base-files -l",
    "front:postbuild": "gulp compress",
    "//-4a": "---------------------------------------------------------------",
    "//-4b": "--------------------------- cypress ---------------------------",
    "//-4c": "---------------------------------------------------------------",
    "cy:open": "cypress open",
    "cy:headless": "cypress run",
    "cy:prettier:base-files": "yarn run prettier \"./cypress/**/*.{js,ts}\"",
    "cy:prettier:fix": "yarn run front:prettier:base-files --write",
    "cy:prettier:check": "yarn run front:prettier:base-files -l",
    "//-5a": "---------------------------------------------------------------",
    "//-5b": "--------------------------- common ----------------------------",
    "//-5c": "---------------------------------------------------------------",
    "all:check": "yarn run back:check && yarn run front:check && yarn run cy:prettier:check",
    "all:check:fix": "yarn run back:check:fix && yarn run front:check:fix && yarn run cy:prettier:fix",
    "//-6a": "---------------------------------------------------------------",
    "//-6b": "--------------------------- hooks -----------------------------",
    "//-6c": "---------------------------------------------------------------",
    "precommit": "lint-staged",
    "prepush": "yarn run back:lint:check && yarn run front:lint:check"
  },

My intent here is not to clarify one line, just to have some sort of delimiters between my scripts for backend, frontend, all, etc.

I'm not a huge fan of 1a, 1b, 1c, 2a, ... but the keys are different and I do not have any problem at all like that.


So far, most "hacks" here suggest to abuse JSON. But instead, why not abuse the underlying scripting language?

Edit The initial response was putting the description on the right using # add comments here to wrap it; however, this does not work on Windows, because flags (e.g., npm run myframework -- --myframework-flags) would be ignored. I changed my response to make it work on all platforms, and added some indents for readability purposes.

{
 "scripts": {
    "help": "       echo 'Display help information (this screen)';          npm run",
    "myframework": "echo 'Run myframework binary';                          myframework",
    "develop": "    echo 'Run in development mode (with terminal output)';  npm run myframework"
    "start": "      echo 'Start myFramework as a daemon';                   myframework start",
    "stop":  "      echo 'Stop the myFramework daemon';                     myframework stop"
    "test": "echo \"Error: no test specified\" && exit 1"
  }
}

This will:

  1. Not break JSON compliance (or at least it's not a hack, and your IDE will not give you warnings for doing strange, dangerous stuff)
  2. Works cross platform (tested on macOS and Windows, assuming it would work just fine on Linux)
  3. Does not get in the way of running npm run myframework -- --help
  4. Will output meaningful info when running npm run (which is the actual command to run to get information about available scripts)
  5. Presents a more explicit help command (in case some developers are not aware that npm run presents such output)
  6. Will show both the commands and its description when running the command itself
  7. Is somewhat readable when just opening package.json (using less or your favorite IDE)

I like this:

  "scripts": {
    "??? Jenkins Build - in this order ???                                                                                                  ": "",
    "purge": "lerna run something",
    "clean:test": "lerna exec --ignore nanana"
}

There are extra spaces in the command name, so in Visual Studio Code's NPM Scripts plugin you have a better look.


Inspired by this thread, here's what we are using:

{
  "//dependencies": {
    "crypto-exchange": "Unified exchange API"
  },
  "dependencies": {
    "crypto-exchange": "^2.3.3"
  },
  "//devDependencies": {
    "chai": "Assertions",
    "mocha": "Unit testing framwork",
    "sinon": "Spies, Stubs, Mocks",
    "supertest": "Test requests"
  },
  "devDependencies": {
    "chai": "^4.1.2",
    "mocha": "^4.0.1",
    "sinon": "^4.1.3",
    "supertest": "^3.0.0"
  }
}

Here's my take on comments within package.json / bower.json:

I have file package.json.js that contains a script that exports the actual package.json. Running the script overwrites the old package.json and tells me what changes it made, perfect to help you keep track of automatic changes npm made. That way I can even programmatically define what packages I want to use.

The latest Grunt task is here: https://gist.github.com/MarZab/72fa6b85bc9e71de5991


I have a funny hack idea.

Create an npm package name suitably as a comment divider for dependencies and devDependencies block in file package.json, for example x----x----x

{
    "name": "app-name",
    "dependencies": {
        "x----x----x": "this is the first line of a comment",
        "babel-cli": "6.x.x",
        "babel-core": "6.x.x",
        "x----x----x": "this is the second line of a comment",
        "knex": "^0.11.1",
        "mocha": "1.20.1",
        "x----x----x": "*"
    }
}

NOTE: You must add the last comment divider line with a valid version, like * in the block.


NPS (Node Package Scripts) solved this problem for me. It lets you put your NPM scripts into a separate JavaScript file, where you can add comments galore and any other JavaScript logic you need to. https://www.npmjs.com/package/nps

Sample of the package-scripts.js from one of my projects

module.exports = {
  scripts: {
    // makes sure e2e webdrivers are up to date
    postinstall: 'nps webdriver-update',

    // run the webpack dev server and open it in browser on port 7000
    server: 'webpack-dev-server --inline --progress --port 7000 --open',

    // start webpack dev server with full reload on each change
    default: 'nps server',

    // start webpack dev server with hot module replacement
    hmr: 'nps server -- --hot',

    // generates icon font via a gulp task
    iconFont: 'gulp default --gulpfile src/deps/build-scripts/gulp-icon-font.js',

    // No longer used
    // copyFonts: 'copyfiles -f src/app/glb/font/webfonts/**/* dist/1-0-0/font'
  }
}

I just did a local install npm install nps -save-dev and put this in my package.json scripts.

"scripts": {
    "start": "nps",
    "test": "nps test"
}

As duplicate comment keys are removed running package.json tools (npm, yarn, etc.), I came to using a hashed version which allows for better reading as multiple lines and keys like:

"//": {
  "alpaca": "we use the bootstrap version",
  "eonasdan-bootstrap-datetimepicker": "instead of bootstrap-datetimepicker",
  "moment-with-locales": "is part of moment"
},

which is 'valid' according to my IDE as a root key, but within dependencies it complains expecting a string value.


As this answer explains, the // key was reserved, so it can be used conventionally for comments. The problem with // comment is that it's not practical, because it can't be used multiple times. Duplicate keys are deleted on package.json automatic updates:

"//": "this comment about dependencies stays",
"dependencies": {}
"//": "this comment disappears",
"devDependencies": {}

Another problem is that // comment can't be used inside dependencies and devDependencies because it's treated as a regular dependency:

"dependencies": {
  "//": "comment"
}

npm ERR! code EINVALIDPACKAGENAME

npm ERR! Invalid package name "//": name can only contain URL-friendly characters

A workaround that works in NPM, but not Yarn, is to use a non-string value:

"dependencies": {
  "foo": ["unused package"],
}

A workaround that works in NPM and Yarn is a comment added as a part of semantic versioning:

"dependencies": {
  "bar": "^2",
  "foo": "^2 || should be removed in 1.x release"
}

Notice that if the first part before OR doesn't match, versions from a comment can be parsed, e.g. 1.x.

Packages that need to be commented, but not installed, should be moved to another key, e.g. dependencies //:

"dependencies //": {
  "baz": "unused package",
}

You can always abuse the fact that duplicated keys are overwritten. This is what I just wrote:

"dependencies": {
  "grunt": "...",
  "grunt-cli": "...",

  "api-easy": "# Here is the pull request: https://github.com/...",
  "api-easy": "git://..."

  "grunt-vows": "...",
  "vows": "..."
}

However, it is not clear whether JSON allows duplicated keys (see Does JSON syntax allow duplicate keys in an object?. It seems to work with npm, so I take the risk.

The recommened hack is to use "//" keys (from the nodejs mailing list). When I tested it, it did not work with "dependencies" sections, though. Also, the example in the post uses multiple "//" keys, which implies that npm does not reject JSON files with duplicated keys. In other words, the hack above should always be fine.

Update: One annoying disadvantage of the duplicated key hack is that npm install --save silently eliminates all duplicates. Unfortunately, it is very easy to overlook it and your well-intentioned comments are gone.

The "//" hack is still the safest as it seems. However, multi-line comments will be removed by npm install --save, too.


For npm's package.json, I have found two ways (after reading this conversation):

  "devDependencies": {
    "del-comment": [
      "some-text"
    ],
    "del": "^5.1.0 ! inner comment",
    "envify-comment": [
      "some-text"
    ],
    "envify": "4.1.0 ! inner comment"
  }

But with the update or reinstall of package with "--save" or "--save-dev, a comment like "^4.1.0 ! comment" in the corresponding place will be deleted. And all this will break npm audit.


I do something that's some of you might like:

This // inside of the name means it's a comment for me:

  "//":"Main and typings are used till ES5",
  "//main": "build/index",
  "//typings": "build/index",

Another hack

I created a script to read file package.json as the context for a handlebars template.

The code is below, in case someone finds this approach useful:

const templateData = require('../package.json');
const Handlebars = require('handlebars');
const fs = require('fs-extra');
const outputPath = __dirname + '/../package-json-comments.md';
const srcTemplatePath = __dirname + '/package-json-comments/package-json-comments.hbs';

Handlebars.registerHelper('objlist', function() {
  // The first argument is an object, and the list is a set of keys for that obj
  const obj = arguments[0];
  const list = Array.prototype.slice.call(arguments, 1).slice(0,-1);

  const mdList = list.map(function(k) {
    return '* ' + k + ': ' + obj[k];
  });

  return new Handlebars.SafeString(mdList.join("\n"));
});

fs.readFile(srcTemplatePath, 'utf8', function(err, srcTemplate){
  if (err) throw err;
  const template = Handlebars.compile(srcTemplate);
  const content = template(templateData);

  fs.writeFile(outputPath, content, function(err) {
    if (err) throw err;
  });
});

handlebars template file package-json-comments.hbs

### Dependency Comments
For package: {{ name }}: {{version}}

#### Current Core Packages
should be safe to update
{{{objlist dependencies
           "@material-ui/core"
           "@material-ui/icons"
           "@material-ui/styles"
}}}

#### Lagging Core Packages
breaks current code if updated
{{{objlist dependencies
           "amazon-cognito-identity-js"
}}}

#### Major version change
Not tested yet
{{{objlist dependencies
           "react-dev-utils"
           "react-redux"
           "react-router"
           "redux-localstorage-simple"

}}}

After wasting an hour on complex and hacky solutions, I've found both simple and valid solution for commenting my bulky dependencies section in package.json. Just like this:

{
  "name": "package name",
  "version": "1.0",
  "description": "package description",
  "scripts": {
    "start": "npm install && node server.js"
  },
  "scriptsComments": {
    "start": "Runs development build on a local server configured by server.js"
  },
  "dependencies": {
    "ajv": "^5.2.2"
  },
  "dependenciesComments": {
    "ajv": "JSON-Schema Validator for validation of API data"
  }
}

When sorted the same way, it's now very easy for me to track these pairs of dependencies/comments either in Git commit diffs or in an editor while working with file package.json.

And no extra tools are involved, just plain and valid JSON.


To summarise all of these answers:

  1. Add a single top-level field called // that contains a comment string. This works, but it sucks because you can't put comments near the thing they are commenting on.

  2. Add multiple top-level fields starting with //, e.g. //dependencies that contains a comment string. This is better, but it still only allows you to make top-level comments. You can't comment individual dependencies.

  3. Add echo commands to your scripts. This works, but it sucks because you can only use it in scripts.

These solutions are also all not very readable. They add a ton of visual noise and IDEs will not syntax highlight them as comments.

I think the only reasonable solution is to generate the package.json from another file. The simplest way is to write your JSON as JavaScript and use Node.js to write it to package.json. Save this file as package.json.mjs, chmod +x it, and then you can just run it to generate your package.json.

#!/usr/bin/env node

import { writeFileSync } from "fs";

const config = {
  // TODO: Think of better name.
  name: "foo",
  dependencies: {
    // Bar 2.0 does not work due to bug 12345.
    bar: "^1.2.0",
  },
  // Look at these beautify comments. Perfectly syntax highlighted, you
  // can put them anywhere and there no risk of some tool removing them.
};

writeFileSync("package.json", JSON.stringify({
    "//": "This file is \x40generated from package.json.mjs; do not edit.",
    ...config
  }, null, 2));

It uses the // key to warn people from editing it. \x40generated is deliberate. It turns into @generated in package.json and means some code review systems will collapse that file by default.

It's an extra step in your build system, but it beats all of the other hacks here.


DISCLAIMER: you probably should not use this hack. See comments below.


Here is another hack for adding comments in JSON. Since:

{"a": 1, "a": 2}

Is equivalent to

{"a": 2}

You can do something like:

{
  "devDependencies": "'mocha' not needed as should be globally installed",
  "devDependencies" :  {
    "should": "*"
  }
}

Since most developers are familiar with tag/annotation-based documentation, the convention I have started using is similar. Here is a taste:

{
  "@comment dependencies": [
    "These are the comments for the `dependencies` section.",
    "The name of the section being commented is included in the key after the `@comment` 'annotation'/'tag' to ensure the keys are unique.",
    "That is, using just \"@comment\" would not be sufficient to keep keys unique if you need to add another comment at the same level.",
    "Because JSON doesn't allow a multi-line string or understand a line continuation operator/character, just use an array for each line of the comment.",
    "Since this is embedded in JSON, the keys should be unique.",
    "Otherwise JSON validators, such as ones built into IDEs, will complain.",
    "Or some tools, such as running `npm install something --save`, will rewrite the `package.json` file but with duplicate keys removed.",
    "",
    "@package react - Using an `@package` 'annotation` could be how you add comments specific to particular packages."
  ],
  "dependencies": {
    ...
  },
  "scripts": {
    "@comment build": "This comment is about the build script.",
    "build": "...",

    "@comment start": [
      "This comment is about the `start` script.",
      "It is wrapped in an array to allow line formatting.",
      "When using npm, as opposed to yarn, to run the script, be sure to add ` -- ` before adding the options.",
      "",
      "@option {number} --port - The port the server should listen on."
    ],
    "start": "...",

    "@comment test": "This comment is about the test script.",
    "test": "..."
  }
}

Note: For the dependencies, devDependencies, etc. sections, the comment annotations can't be added directly above the individual package dependencies inside the configuration object since npm is expecting the key to be the name of an npm package. Hence the reason for the @comment dependencies.

Note: In certain contexts, such as in the scripts object, some editors/IDEs may complain about the array. In the scripts context, Visual Studio Code expects a string for the value -- not an array.

I like the annotation/tag style way of adding comments to JSON because the @ symbol stands out from the normal declarations.