CLI tool
lint-staged/lint-staged avatar
lint-staged/lint-staged

lint-staged: run formatters and linters only on staged git files

🚫💩 — Run tasks like formatters and linters against staged git files

14,736 stars472 forksJavaScriptMIT

At a glance

What is it?
lint-staged is a Node.js CLI that runs shell tasks against the files you have staged for a commit, filtered by glob pattern. It is small, MIT licensed, and its main cost is that it rewrites your working tree while it runs.
Who is it for?
Adopt lint-staged if you already run ESLint, Prettier, Stylelint or similar tools in a Node project and want them applied only to the files in a commit. Do not adopt it as a general pre-commit framework for non-JavaScript repositories, and do not use it expecting it to manage git hooks, because the README hands that job to Husky.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 4 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The problem lint-staged solves is scope, not linting

Running ESLint or Prettier over an entire repository is slow, and on a large codebase the output is mostly noise about files nobody touched in the current change. lint-staged narrows the input. Its own description in package.json is blunt: "Lint files staged by git". The README frames the same idea as a question of relevance, arguing that you "only want to check files that will be committed".

The audience is therefore specific. You need a git repository, a Node.js toolchain, and at least one formatter or linter that accepts a list of file paths as arguments. If your checks are project-wide by nature, such as a TypeScript compiler run or an integration test suite, filtering by staged files does not help and may produce misleading partial results. The README makes that boundary explicit by pointing readers who want to check staged files without editing them at a separate shell script, lint-staged.sh.

How lint-staged manipulates git state to isolate a commit

The mechanism is more invasive than the name suggests, and the README does not hide it. Before running anything, lint-staged backs up the original state, which by default means creating a git stash. The sample terminal output in the README walks through the phases in order: backing up original state, running tasks for staged files, staging changes from tasks, then cleaning up temporary files.

That ordering explains the partial-staging behaviour. If a file has both staged and unstaged edits, lint-staged hides the unstaged portion while the task runs, so a formatter cannot rewrite lines you did not intend to commit. The flag list confirms this is on by default via --no-hide-partially-staged, with --hide-unstaged and --hide-all available for broader hiding. The same design is why --all implies --no-stash: including every tracked file removes the need to protect a staged subset, and it also implies --allow-empty so a run on a clean tree does not fail.

Because these are real git operations, the README carries a caution that lint-staged runs git operations affecting the files in your repository, and documents recovery from the automatic backup:

bash
git stash list --format="%h %s"
# <git-hash> On main: lint-staged automatic backup
git apply --index <git-hash>

If a task fails midway, the default --no-revert behaviour means lint-staged reverts to the original state. The flag exists so you can turn that off, and the README does not describe what you are expected to do after disabling it.

Installing lint-staged and running a first commit

The README's installation path has four steps: install lint-staged, configure a pre-commit git hook, install tools like ESLint or Prettier, and write the configuration. Husky is named as "a popular choice" for the hook, which means lint-staged itself does not install or manage hooks. Start with the package:

bash
npm install --save-dev lint-staged

Next, add a configuration. The README gives this minimal example, which maps a glob to a command:

json
{ "*.js": "eslint" }

That object can live in package.json or in a dedicated config file; the --config flag accepts a path, or - to read from stdin. Then wire the hook. With Husky, the pre-commit hook runs the binary, and the README notes you should commit changes to package.json and .husky so the setup is shared with the team. Now stage a file and commit. What you should see is the phase-by-phase output from the README: a backup line with a short hash, a per-glob task listing such as "*.{json,md}" with a file count, the task output, then staging and cleanup confirmations. If nothing is staged, lint-staged has nothing to run against.

Configuration shape, concurrency and the monorepo question

A config value can be a single command string or an array, and the README documents sequences of commands run in order. Concurrency is controlled by -p, which takes a number or false for serial execution, and defaults to true. Parallelism is per task group, so two globs that both match the same file can run at once; the README does not promise ordering across groups.

Two flags matter more than they look. --max-arg-length exists because passing a very large file list as one command-line argument can exceed the operating system limit, and the default of 0 means no splitting until you set it. --relative passes relative filepaths to tasks, which changes what your tool prints and can matter when a linter resolves config by path. --continue-on-error runs every task to completion instead of stopping at the first failure, which is useful for seeing all problems in one pass but changes the exit behaviour you get in CI.

For monorepos, the relevant lever is --cwd, which runs all tasks in a specific directory instead of the current one. The README documents the flag but does not provide a worked monorepo recipe, so expect to test how your globs resolve relative to that directory before trusting it.

Where lint-staged is the wrong tool

The most common failure mode is a partial commit that passes while the branch does not. lint-staged only sees staged files, so a change that breaks a file you did not stage, or a rename that breaks an import elsewhere, sails through the hook. That is inherent to the design, not a bug, and it is why the hook should sit alongside CI rather than replace it.

The second limitation is the git manipulation itself. Because lint-staged stashes, hides and re-stages, it is a poor fit for workflows that already script git state, such as a commit process that depends on the exact index contents at hook time. The --diff flag, which overrides the default --staged flag of git diff, implies --no-stash, so using it deliberately gives up the automatic backup. Anyone reaching for that flag is opting out of the safety net the README advertises.

Finally, the project is Node-only. The engines field in package.json requires node >=22.22.1, and the package is published as an ES module. In a Python or Go repository where you want a pre-commit runner, lint-staged adds a Node runtime for no benefit. The README does not document rollback for a task that has already modified files and then been interrupted by something outside lint-staged's control, such as a killed process.

lint-staged vs lefthook and Husky

The distinction people get wrong is that Husky and lint-staged are not competitors. Husky installs and manages git hooks; lint-staged is the command you put inside a hook. You can use Husky without lint-staged, and lint-staged works with any hook mechanism, including a hand-written .git/hooks/pre-commit script. The README treats Husky as a recommendation, not a dependency, and package.json lists no Husky runtime dependency.

lefthook is the closer alternative, because it is a hook manager that also runs commands against staged files. The difference in approach is the runtime: lefthook is a standalone binary, so it does not require Node, and it owns both the hook installation and the staged-file filtering that lint-staged splits across two tools. If your repository is polyglot, that single-binary model avoids adding a Node toolchain purely for commit hooks. If your repository is already Node and your checks are ESLint and Prettier, lint-staged keeps the configuration in the ecosystem you already have, expressed as glob-to-command pairs in JSON or a JS config file.

Licence, release cadence and upgrade cost

lint-staged is MIT licensed, which permits commercial use, modification and redistribution provided the copyright notice and permission notice are retained. That is a permissive arrangement, and nothing in the repository suggests dual licensing or a contributor licence agreement that would change it. This is a description of the licence text, not legal advice.

Upgrade cost is shaped by the project's release process. The repository uses Changesets, with a .changeset directory and scripts named version and tag that run changeset version and changeset tag, and MIGRATION.md exists specifically for breaking changes. Recent releases include v17.5.0 on 2026-09-05 and v17.5.1 on 2026-09-10, so patch releases arrive frequently; the last push to the repository was on 2026-09-12. The Node engine requirement of >=22.22.1 is the constraint most likely to block an upgrade, since it moves with major versions and forces a runtime bump before the package will install cleanly. Read MIGRATION.md rather than the changelog alone when crossing a major version.

Editorial conclusion

Adopt lint-staged if you already run ESLint, Prettier, Stylelint or similar tools in a Node project and want them applied only to the files in a commit. Do not adopt it as a general pre-commit framework for non-JavaScript repositories, and do not use it expecting it to manage git hooks, because the README hands that job to Husky. Before rolling it out to a team, verify two things: that node satisfies the engines field in package.json, and that a deliberately failing task leaves your working tree intact via the automatic stash.

Frequently asked questions

how to use lint-staged

Install it with npm install --save-dev lint-staged, set up a pre-commit git hook (the README names Husky as a popular choice), install your linters or formatters, and add a configuration such as { "*.js": "eslint" }. Then stage a few files and commit; lint-staged backs up the original state, runs the tasks against the matching staged files, and stages the results.

how to setup husky and lint staged

Husky configures the git hook and lint-staged is the command the hook runs. After installing both, add a pre-commit hook that invokes npx lint-staged, and commit the changes to package.json and .husky so the rest of the team gets the same setup.

what does lint staged do

It runs arbitrary shell tasks against the list of staged git files, filtered by a glob pattern. The README's stated aim is to check only the files that will be committed, so a formatter or linter does not have to process the whole project.

what is npx lint staged

npx lint-staged runs the lint-staged binary without a global install, which is why it appears in hook definitions. The package exposes bin/lint-staged.js as its command, and --help prints the full flag list.

lint staged vs lefthook

lint-staged is a Node package that runs tasks on staged files and leaves hook installation to something like Husky. lefthook is a standalone binary that handles both the hook and the staged-file commands, which avoids adding a Node runtime in a polyglot repository.

Official sources

  1. License: MIT
  2. lint-staged/lint-staged on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lint-staged-lint-staged.svg)](https://hysenlabs.com/projects/lint-staged-lint-staged)