Open-source project
webpro-nl/knip avatar
webpro-nl/knip

Knip: finding unused files, dependencies and exports in JavaScript and TypeScript projects

✂️ Find unused files, dependencies and exports in your JavaScript and TypeScript projects. Knip it before you ship it!

12,377 stars504 forksTypeScriptISC

At a glance

What is it?
Knip is a TypeScript-based linter for dead code in JS/TS projects, published on npm as knip and licensed under ISC. It is opinionated about entry points and configuration, and that is where most of its friction lives.
Who is it for?
Adopt Knip if you maintain a JavaScript or TypeScript project with a package.json, a lockfile and a build you can describe in configuration, and if you are willing to keep knip.json in sync as the project grows. Do not adopt it if your entry points are generated at runtime in ways you cannot enumerate, or if you expect it to replace a type checker or a bundler's tree shaking.
Can I use it commercially?
Yes. ISC 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 received new commits within the last day.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

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

DEEP OPEN-SOURCE ANALYSIS

What problem Knip addresses, and whose projects it fits

JavaScript and TypeScript projects accumulate three kinds of residue: files nothing imports, dependencies listed in package.json that no source file references, and exported symbols that no other module consumes. None of these break a build. They survive code review because they look like normal code. Knip's stated purpose is to find and fix unused dependencies, exports and files, and the README frames the payoff as less code, less maintenance and easier refactorings rather than a runtime speedup.

The project targets people who already have a package.json and a module graph worth analysing. That includes application repositories, libraries with a build step, and monorepos, since the repository layout shows a pnpm workspace with a packages/ directory and a root knip.json. It is a development-time tool. It reads your source and your manifests; it does not ship in your bundle and it does not run in production.

The name is Dutch: the README says it means "(to) cut" and is pronounced with a hard K. The project is authored by Lars Kappert under webpro-nl, with the homepage at knip.dev and the repository at github.com/webpro-nl/knip.

How Knip builds its picture of the module graph

Knip starts from entry files and walks outward. Anything reachable from an entry point is considered live; anything not reachable is a candidate for removal. The hard part is not the walk, it is deciding what counts as an entry. Framework conventions, test runners, config files and scripts all introduce entry points that no import statement points at, so the tool needs to know about them.

That is why configuration is central rather than optional. The repository itself carries a knip.json at the root, and the README lists @knip/create-config as an official package alongside knip, @knip/language-server and @knip/mcp. The existence of a dedicated config generator package suggests the maintainers expect configuration to be the first obstacle a new user hits. The language server and MCP packages indicate the same analysis is exposed to editors and to agent tooling, not only to a terminal command.

The README credits four projects whose code was partially copied or which served as inspiration: @npmcli/package-json (ISC), @pnpm/deps.graph-sequencer (MIT), file-entry-cache (MIT) and json-parse-even-better-errors (MIT). The dependency graph sequencing and the file entry cache are the interesting ones here, because they hint at how the traversal and its caching are implemented, though the README does not describe either in detail.

Installing Knip and running a first analysis

The project is published on npm as knip, so installation goes through your package manager. The root package.json declares engines.node as ">=22.0.0", which means the Node runtime you run Knip with must be 22 or newer regardless of what your application targets. The root package.json also declares "packageManager": "[email protected]", which is how the repository itself is installed.

The README does not print an install command, so the package name is the authoritative detail: knip on npm. The repository installs it as a workspace package and exposes it through its own scripts, which is the pattern the root package.json documents:

bash
pnpm install

The README does not show a CLI invocation either. What it does show is that the tool is exposed as an official npm package named knip, and that a config generator exists as @knip/create-config. Once installed, the binary name matches the package name, which is the convention the npm listing follows:

bash
npx knip

The output is a report of unused files, dependencies and exports. Treat the first run as a survey rather than a verdict: read the list, and for each entry decide whether it is genuinely dead or whether Knip simply did not know it was an entry point.

When the defaults do not match your project, the official config package generates a starting point. The README lists @knip/create-config as an official npm package, and the repository keeps its own knip.json at the root as a working example of the format:

bash
npx @knip/create-config

If you prefer to install globally instead of per project, the same binary is available from the npm package, but a dev dependency is the safer default because it ties the analysis to a specific version.

The entry-point problem is Knip's real limitation

Every dead-code tool that works by reachability inherits the same failure mode: a false positive is indistinguishable from a true positive in the output. If Knip does not know that a file is loaded by a framework router, a CLI argument parser, a dynamic import with a computed path, or a test runner's convention, it will report that file as unused. Acting on the report then deletes working code.

Knip's answer is configuration, and configuration is a maintenance obligation. Every new tool added to a repository, every new convention, every generated file is a potential new entry point that the config does not mention. The README does not document an automatic way to detect these; it points at the config package and the website. That is a fair design for a tool whose accuracy depends on knowing your build, but it means adoption cost is not the install command, it is the ongoing accuracy of knip.json.

There is a second boundary worth stating plainly. Knip analyses imports and exports. It is not a type checker and it does not evaluate runtime behaviour, so code that is reachable only through reflection, string-based lookup or an external configuration file falls outside what a static import graph can see. The README does not claim otherwise, but users coming from a linter mindset may expect more coverage than the approach can deliver.

Knip compared with ESLint and with bundler tree shaking

The most common comparison is with ESLint, and the two tools answer different questions. ESLint's no-unused-vars rule operates inside a single file: it flags a local variable that is declared and never read. Knip operates across files: it flags an export that no other module imports, a file that no module reaches, and a dependency in package.json that no source file references. A project can pass ESLint cleanly and still carry hundreds of unreferenced exports, because each one is used locally or not used at all within its own module.

Bundler tree shaking is a different mechanism again, and it is not a substitute. A bundler removes unreachable code from the bundle it produces, but it does not tell you that the source is dead, it does not touch package.json, and it does not report anything. Knip produces a report you can act on. The trade-off is that a bundler sees the real build graph, including whatever plugins and loaders you configured, while Knip sees the graph you described to it. That is why a bundler rarely produces false positives about entry points and Knip sometimes does. The two are complementary: Knip tells you what to delete, the bundler confirms the deletion did not change the output.

Maintenance, release cadence and the ISC licence

The repository is not archived, and the last push was on 2026-09-21. Recent releases include [email protected] on 2026-09-18, [email protected] on 2026-09-16 and [email protected] on 2026-09-09, which shows a steady patch-and-minor cadence rather than long gaps. The project is published under the ISC licence, a permissive licence that the README also uses to describe the copied @npmcli/package-json code. The other three credited projects are MIT.

For adopters, the practical consequence of that cadence is a version to track. Knip runs against your source tree, so a new release can change what it reports even when your code has not changed. Pinning the version in devDependencies and upgrading deliberately, rather than floating, keeps the report stable between upgrades. The ISC and MIT terms are permissive and impose no copyleft obligation on your own code, but this is a description of the licence text, not legal advice; if you redistribute Knip itself or vendored portions of it, read the LICENSE file in the repository and the licences of the four credited projects.

The monorepo structure, with packages/ and a pnpm workspace, means the published packages are built from this repository rather than from a separate release branch. The root package.json includes a release script and release-it tooling, and the CI script runs oxlint, an oxfmt check, installed-check and a line-endings check.

Who should run Knip, and what to check before the first cleanup

Knip fits teams that already treat dependency hygiene as part of maintenance and have the appetite to maintain a config file. It fits libraries especially well, because an unused export in a published library is part of your public surface and removing it is a semver decision, not just a cleanup. It fits monorepos, where unused workspace dependencies are easy to accumulate and hard to notice.

It fits poorly when the build is not statically describable. If your project loads modules through a plugin registry keyed by strings, or generates entry points during the build, the reachability graph Knip constructs will be wrong in ways the config cannot fully express, and you will spend more time suppressing findings than acting on them. It is also the wrong tool if what you actually want is a type error report or a smaller bundle; those are the jobs of a type checker and a bundler respectively.

Before deleting anything, verify two things. First, that the entry list in your configuration covers every way your project is actually started, including tests, scripts and framework conventions. Second, that any export reported as unused is not re-exported through a barrel file or consumed by a package outside the repository, since those are the cases where a static graph most often diverges from reality. The VS Code and Open VSX extensions listed in the README let you inspect findings in the editor rather than in a terminal, which makes that verification faster.

Editorial conclusion

Adopt Knip if you maintain a JavaScript or TypeScript project with a package.json, a lockfile and a build you can describe in configuration, and if you are willing to keep knip.json in sync as the project grows. Do not adopt it if your entry points are generated at runtime in ways you cannot enumerate, or if you expect it to replace a type checker or a bundler's tree shaking. Before trusting a first run, verify the entry list against your actual build inputs and check whether the reported unused exports are re-exported through a barrel file that Knip did not follow.

Frequently asked questions

What is the knip package?

Knip is an npm package that finds unused dependencies, exports and files in JavaScript and TypeScript projects. The README describes it as cutting unused code so that projects have less to maintain, and it is published under the ISC licence.

What does knip mean?

The README states that the name is Dutch and means "(to) cut", pronounced with a hard K.

How do I install and run Knip?

Install the knip package from npm as a dev dependency and run it with npx, which analyses the current project. The root package.json requires Node 22 or newer, and the README lists @knip/create-config as the official package for generating a starting configuration.

Does Knip work in a monorepo?

The Knip repository itself is a pnpm workspace with a packages/ directory and a root knip.json, so the tool is used in a monorepo layout. The README does not document monorepo-specific behaviour beyond the configuration file.

What licence is Knip released under?

Knip is licensed under the ISC License according to the README and the root package.json. The README also credits four projects whose code was partially copied or which inspired parts of Knip, three of them under MIT.

Official sources

  1. License: ISC
  2. Project website
  3. README
  4. Releases
  5. webpro-nl/knip on GitHub
For maintainers

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/webpro-nl-knip.svg)](https://hysenlabs.com/projects/webpro-nl-knip)
Community notes

Community notes