Skip to content
LocalOnly
JSON Tools

How to Format JSON Files Automatically with Prettier

Two formatters can disagree about the same JSON file. Here is how a team picks one, applies it in every editor and before every commit, and makes CI enforce it.

Updated October 18, 20264 min readTool runs in your browser

Formatting JSON files automatically is the only reliable way to keep them consistent. A JSON file that several people edit tends to drift. One editor indents with four spaces, another with two, a script rewrites the whole file with JSON.stringify, and pull requests start to carry lines that changed only because of whitespace. Reviewers learn to skim those diffs, which is exactly when a real change slips through.

The fix is not a better formatter. It is one agreed formatter, applied the same way in every editor, before every commit and in CI. This guide sets that up with EditorConfig, Prettier, a pre-commit hook and a CI check. For a single file you just want to read, the JSON Formatter does the job in the browser.

Two formatters, two different files

It helps to see why the choice of tool matters more than the choice of indentation. Here is the same small settings file run through two common formatters.

JSON.stringify(value, null, 2) expands every object and array
{
  "name": "demo",
  "tags": [
    "a",
    "b"
  ],
  "owner": {
    "id": 7,
    "active": true
  }
}
Prettier puts the same object on one line because it fits
{ "name": "demo", "tags": ["a", "b"], "owner": { "id": 7, "active": true } }

Both are valid and both are readable, but when one tool rewrites a file the other one produced, every line shows up in the diff. Prettier's rule is worth knowing: it keeps an object expanded if the source has a line break between the opening brace and the first key. Otherwise it puts the object on one line whenever it fits within the print width, which is 80 characters by default.

So the real team decision is which tool owns the JSON files. The rest of this guide uses Prettier, because the same configuration runs in editors, in a pre-commit hook and in CI.

Set the basics in .editorconfig

EditorConfig is a small file in the repository root that most editors read to set indentation, line endings and the final newline. JetBrains IDEs support it out of the box. VS Code needs the EditorConfig extension.

.editorconfig
root = true

[*.json]
indent_style = space
indent_size = 2
insert_final_newline = true

Prettier reads the same file. With indent_size = 4 and no Prettier configuration at all, Prettier indents JSON with four spaces, so the setting lives in one place instead of two.

Format JSON with Prettier

Install Prettier as an exact dev dependency so every machine and the CI job run the same version. Output can change between major versions, and a pinned version keeps the editors and the check in agreement.

npm install --save-dev --save-exact prettier
npx prettier --write "**/*.json"

Prettier skips node_modules and anything listed in .gitignore or .prettierignore. Put generated JSON, such as fixtures written by a script, in .prettierignore, so the generator and Prettier do not keep rewriting each other's output.

Two behaviours to expect. Prettier keeps // and /* */ comments, which matters for files such as tsconfig.json. It also removes trailing commas from .json files, so a hand-edited config may lose a few commas the first time it is formatted.

Keep git blame useful

Make the first prettier --write a commit of its own, with no other changes. Then add that commit hash to a .git-blame-ignore-revs file in the repository root, so blame views skip the reformatting commit. GitHub reads that file automatically, and locally you can point git config blame.ignoreRevsFile at it.

Format staged files before each commit

Editor settings catch most edits, but not files written by scripts or changed in a terminal. A pre-commit hook formats whatever is staged. With husky and lint-staged, install both and let husky create the hook:

npm install --save-dev husky lint-staged
npx husky init

husky init creates .husky/pre-commit. Replace its contents with npx lint-staged, then tell lint-staged what to run for JSON files in package.json:

package.json (excerpt)
"lint-staged": {
  "*.json": "prettier --write"
}

lint-staged runs Prettier only on the staged files and adds the formatted result to the commit. Anyone can skip hooks with git commit --no-verify, which is why the CI check in the next section still matters.

Fail the build on unformatted JSON

The last line of defence is a CI job that checks without changing anything. prettier --check lists every file that does not match and exits with code 1:

$ npx prettier --check "**/*.json"
Checking formatting...
[warn] config/settings.json
[warn] Code style issues found in the above file. Run Prettier with --write to fix.

In GitHub Actions the whole job is a few lines:

.github/workflows/format.yml
name: Format check
on: [pull_request]
jobs:
  prettier:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx prettier --check "**/*.json"

Keep the quotes around the glob. Without them the shell expands **/*.json before Prettier sees it, and in a default bash setup ** matches only one directory level.

Frequently asked questions

Command Palette

Search for a tool or command