Knip: The Dead Code Detective Your Codebase Deserves
Every project starts clean. A handful of files, a few carefully chosen dependencies, and exports that all serve a purpose. Fast-forward six months of feature work, refactors, and the occasional "we might need this later" commit, and suddenly you have files no one imports, packages listed in package.json that nothing touches, and exports drifting through the codebase like tumbleweeds. Knip exists to find all of that dead weight and help you cut it loose. The name comes from the Dutch word for "cut" -- and that is exactly what it does.
What Makes Knip Sharp
Knip is not a single-purpose linter. It performs a comprehensive sweep of your entire project in one pass, catching six distinct categories of waste:
- Unused files that are not reachable from any entry point
- Unused dependencies sitting in
package.jsonwithout a single import - Unused exports that were once public API but are now orphaned
- Unresolved imports pointing to modules that do not exist
- Unused class and enum members never referenced internally
- Unused type exports in TypeScript that no file consumes
It ships with over 131 plugins that understand the conventions of popular frameworks and tools -- from Next.js and Vite to Jest, Playwright, Storybook, Prisma, and ESLint. This means Knip can automatically detect your project structure and entry points without you writing a single line of configuration.
Getting Knip Into Your Toolbelt
Install knip as a dev dependency alongside TypeScript and Node types:
npm install -D knip typescript @types/node
or
yarn add -D knip typescript @types/node
Then add a script to your package.json:
// package.json (scripts section)
{
"scripts": {
"knip": "knip"
}
}
If you want an even faster start, the setup wizard will auto-configure everything:
npm init @knip/config
Taking the First Cut
Running Your First Scan
With zero configuration, Knip already knows what to look for. Run it and see what it finds:
npx knip
The output groups issues by category. You will see unused files listed first, then unused dependencies, unused exports, and so on. For large codebases, limit the initial noise:
npx knip --max-show-issues 5
This lets you tackle problems in manageable batches instead of staring at a wall of output.
Focusing on One Problem at a Time
Knip supports CLI flags to filter by issue category. If you want to start by cleaning up unused dependencies before worrying about anything else:
npx knip --dependencies
Or if stale files are your main concern:
npx knip --files
This incremental approach works well for existing projects where the initial report might feel overwhelming. Fix one category, commit, and move on to the next.
Production Mode Analysis
Not all code matters equally. Knip can distinguish between production and development concerns. Running in production mode ignores devDependencies, test files, and configuration that only matters during development:
npx knip --production
This is particularly useful as a pre-release check. You care about shipping lean production bundles, and this mode tells you exactly what production code is dead.
Precision Cuts for Complex Projects
Configuring Entry Points and Project Scope
While Knip is smart about auto-detection, sometimes you need to be explicit. Create a knip.json (or knip.jsonc if you like comments) at your project root:
// knip.json
{
"entry": ["src/main.ts", "src/workers/*.ts"],
"project": ["src/**/*.ts", "src/**/*.tsx"],
"ignoreDependencies": ["@internal/dev-tools"]
}
The entry array tells Knip where your application starts. The project array defines which files belong to your project. Anything outside project is ignored, and anything not reachable from entry is flagged as unused. The ignoreDependencies option lets you suppress known false positives for packages that are used in ways Knip cannot detect statically.
Monorepo Workspace Support
Knip has first-class monorepo support. It automatically discovers workspaces from your package.json workspaces field or pnpm-workspace.yaml. Each workspace can have its own configuration:
// knip.json
{
"workspaces": {
".": {
"entry": ["src/index.ts"],
"project": ["src/**/*.ts"]
},
"packages/ui": {
"entry": ["src/index.ts", "src/components/*/index.ts"],
"project": ["src/**/*.{ts,tsx}"]
},
"packages/api": {
"entry": ["src/server.ts"],
"project": ["src/**/*.ts"]
}
}
}
You can also target specific workspaces during analysis, which is handy when you only changed code in one package:
npx knip --workspace packages/ui
Knip shares files across workspace boundaries in a single TypeScript program when configurations allow, which improves performance and enables accurate cross-workspace dependency tracking.
Taming False Positives with Tags
Sometimes an export is intentionally unused locally because it is part of a public API consumed by external packages. Knip supports JSDoc tags to mark these cases:
/**
* @public
*/
export function createClient(config: ClientConfig): Client {
// ...
}
/**
* @internal
*/
export function resetState(): void {
// ...
}
You can then configure Knip to exclude exports with specific tags:
// knip.json
{
"exclude": ["unresolved"],
"ignoreExportsUsedInFile": true,
"tags": ["-internal"]
}
The tags option with a - prefix tells Knip to exclude exports annotated with @internal from the unused exports report. This gives you fine-grained control over what gets flagged without resorting to blanket ignore patterns.
Integrating Knip Into CI
Knip becomes most powerful when it runs automatically. Add it to your CI pipeline so that new unused code never makes it past review:
// .github/workflows/ci.yml (relevant job step)
// steps:
// - name: Check for unused code
// run: npx knip --no-exit-code --reporter json > knip-report.json
// Or fail the build on any issues:
// steps:
// - name: Enforce clean code
// run: npx knip
When Knip finds issues, it exits with a non-zero code by default, which naturally fails CI builds. For projects that want to track progress without blocking merges, the --no-exit-code flag combined with a JSON reporter lets you collect data without failing the pipeline.
The Last Snip
Knip has earned its place as the definitive tool for finding dead code in JavaScript and TypeScript projects. With nearly 5 million weekly downloads and adoption by teams at organizations like Vercel, Adobe, and Datadog, it is well past the experimental phase. Its predecessors -- ts-prune and unimported -- have both been archived, with their maintainers pointing users directly to Knip.
What makes Knip stand out is not just its breadth of detection but its respect for developer time. The plugin system means you rarely need to configure anything. The monorepo support means it scales from side projects to enterprise repositories. And the category-based filtering means you can adopt it incrementally, fixing one class of issues at a time instead of drowning in a sea of warnings on day one.
If your codebase has been growing for more than a few months, there is almost certainly dead code hiding in it. Knip will find it. And once you see how much cleaner things look after a good trim, you will wonder how you ever shipped without it.