Need help with your JSON?
Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool
Automated JSON Formatting in Git Hooks
If JSON files keep showing up in pull requests with mixed indentation, inconsistent spacing, or accidental reordering, a Git pre-commit hook is the right place to fix it. The hook can format staged JSON files before the commit is created, which keeps diffs smaller and avoids style debates in code review.
For most JavaScript and TypeScript repositories today, the best default is Prettier + lint-staged + Husky. That setup formats only staged files, works well with git add --patch, and shares the hook configuration with the rest of the team. If you want fewer Node-specific dependencies, a raw Git hook or Python's pre-commit framework can do the same job.
- Use
Husky + lint-staged + Prettierif your repo already has a Node toolchain. - Use
pre-commitif you want language-agnostic hooks that are easy to pin and update. - Use a raw shell hook only if you want the simplest possible setup and understand the staging caveats.
How Git Hooks Fit In
Git runs hook programs from .git/hooks by default, or from another directory if you configure core.hooksPath. The pre-commit hook runs before the commit object is created. If the hook exits with a non-zero status, Git aborts the commit. Developers can still bypass it with git commit --no-verify, so hooks should be treated as fast local guardrails, not your only enforcement layer.
That matters for JSON because formatting is deterministic and cheap. A hook can pretty-print files, reject invalid JSON, and re-stage the corrected version before the commit lands in history.
Recommended Setup: Husky + lint-staged + Prettier
This is the most practical setup for modern web repos. Prettier's own documentation recommends a pre-commit tool, and specifically calls out lint-staged when you need support for partially staged files.
1. Install the formatter and hook tools
pnpm add -D prettier husky lint-staged pnpm exec husky init
If you use npm, yarn, or bun, use the equivalent install and exec commands. husky init creates a .husky/pre-commit hook and wires Husky into your project setup.
2. Add a lint-staged rule for JSON files
{
"lint-staged": {
"*.json": "prettier --write --ignore-unknown"
}
}3. Run lint-staged from the pre-commit hook
# .husky/pre-commit pnpm exec lint-staged
This gives you the behavior most teams want: only staged JSON files are formatted, the updated files are staged again automatically, and partially staged files are handled much more safely than a hand-rolled git add loop.
- Why Prettier is a good default: it formats JSON consistently without sorting keys.
- Why lint-staged matters: it targets staged files instead of running on the whole repository.
- Why Husky is useful: the hook lives in the repository, so the setup is easy to share.
Minimal Shell Hook for Small Repos
If you do not want extra tooling, a plain shell hook still works well for simple repositories. A good pattern is to keep the hook in a committed directory such as .githooks/ and point Git at it with git config core.hooksPath .githooks.
.githooks/pre-commit
#!/bin/sh files=$(git diff --cached --name-only --diff-filter=ACMR -- '*.json') [ -z "$files" ] && exit 0 old_ifs=$IFS IFS=' ' for file in $files; do prettier --write --ignore-unknown "$file" || exit 1 git add "$file" || exit 1 done IFS=$old_ifs exit 0
Swap prettier for jq or another formatter if your project is not Node-based. Just be careful with tools that sort keys automatically. For example, jq -S and the default behavior of some hook formatters can reorder object keys, which may create noisy diffs if your team prefers the original logical order.
The tradeoff is staging behavior: a manual hook usually reformats the entire file and re-adds it. That is acceptable for simple workflows, but it is not the safest choice if contributors frequently use git add --patch.
Python pre-commit Alternative
If your team already uses the pre-commit framework, the built-in JSON hooks are a solid option. This keeps formatter versions pinned in one file and works well across polyglot repositories.
.pre-commit-config.yaml
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: check-json
- id: pretty-format-json
args: [--autofix, --no-sort-keys]In this setup, check-json rejects invalid files and pretty-format-json rewrites them in place. The explicit --no-sort-keys flag avoids surprise key reordering. After adding the file, run pre-commit install, and use pre-commit autoupdate occasionally to refresh pinned hook versions.
Common Pitfalls and Current Best Practices
- Partially staged files: this is the main reason to prefer
lint-stagedor, in more advanced cases,git-format-staged. Raw hooks that run a formatter and thengit addcan stage changes the developer did not mean to include. - Invalid JSON: the formatter should fail the commit cleanly. That is useful signal, not friction. Fix the syntax, re-stage the file, and commit again.
- Generated or huge JSON files: skip files that are machine-generated, vendored, or so large that formatting them on every commit becomes slow and noisy.
- Shared config: keep your formatting rules in one committed place such as
.prettierrc,package.json, or.pre-commit-config.yaml. - CI still matters: because hooks can be bypassed with
--no-verify, add a formatter or validation check in CI if JSON consistency is important for the repository.
Bottom Line
If you want the least surprising setup, use Prettier + lint-staged + Husky. If you want a language-agnostic workflow, use pre-commit with check-json and pretty-format-json. Reserve raw shell hooks for smaller repos where you control the workflow and understand the tradeoffs.
The important part is not the specific tool. It is making JSON formatting automatic, predictable, and fast enough that contributors stop thinking about it.
Need help with your JSON?
Try our JSON Formatter tool to automatically identify and fix syntax errors in your JSON. JSON Formatter tool