Knip finds unused files, dependencies and exports in your JavaScript and TypeScript projects. Less code and dependencies lead to improved performance, less maintenance and easier refactorings.
export const myVar = true;
This is where ESLint stops: it handles files in isolation, so it does not know whether myVar
is used somewhere else.
This is where Knip starts: it lints the project as a whole and finds unused exports, files and dependencies.
It's only human to forget to remove things that you no longer use. But how do you find out? Where do you start finding things that can be removed?
The dots don't connect themselves. This is where Knip comes in:
- Finds unused files, dependencies and exports
- Finds used dependencies not listed in
package.json
- Built-in support for workspaces (monorepos)
- Growing list of built-in plugins
- Use compilers to include other file types (e.g.
.mdx
,.vue
,.svelte
) - Finds binaries and dependencies in npm scripts, and a lot more locations
- Finds unused members of classes and enums
- Finds duplicate exports
- Use production and strict mode to focus on isolated production code
- Supports any combination of JavaScript and TypeScript
- Multiple built-in reporters (or use custom reporters and preprocessors)
- Understands JSDoc/TSDoc tags (e.g.
@public
and@internal
) - Run Knip as part of your CI environment to detect issues and prevent regressions
Knip shines in both small and large projects. It's a fresh take on keeping your projects clean & tidy!
“An orange cow with scissors, Van Gogh style” - generated
with OpenAI
For updates or questions, come hang out in The Knip Barn (Discord), or follow @webprolific (Twitter) or @webpro (fosstodon.org). Please use GitHub to report issues.
- Getting Started
- Configuration
- Production Mode
- Output
- Fixing Issues
- JSDoc tags
- Command Line Options
- Potential boost with
--no-gitignore
- Comparison & Migration
- Projects using Knip
- Articles, etc.
- Why "Knip"?
- Really, another unused file/dependency/export finder?
- Contributors
npm install -D knip
Knip supports LTS versions of Node.js, and currently requires at least Node.js v16.17 or v18.6.
Since v2.33.0, the Bun runtime is also supported.
Knip has good defaults and aims for no or little configuration. The (simplified) default config:
{
"entry": ["index.js", "src/index.js"],
"project": ["**/*.js"]
}
There's more, jump to Entry Files for details.
Knip looks for this configuration file (ordered by priority):
knip.json
knip.jsonc
.knip.json
.knip.jsonc
knip.ts
knip.js
package.json#knip
You can use a dynamic knip.ts
with TypeScript if you prefer:
import type { KnipConfig } from 'knip';
const config: KnipConfig = {
entry: ['src/index.ts'],
project: ['src/**/*.ts'],
};
export default config;
And if you need, you can also expose an (async) function if you need, like so:
import type { KnipConfig } from 'knip';
const config = async (): Promise<KnipConfig> => ({
entry: ['src/index.ts'],
project: ['src/**/*.ts'],
});
export default config;
Use --config path/to/knip.config.json
for a different path.
Run the checks with npx knip
. Or first add this script to package.json
:
{
"scripts": {
"knip": "knip"
}
}
Then use npm run knip
to analyze the project and output unused files, dependencies and exports.
Knip works just fine with yarn
or pnpm
or bun
.
See Command Line Options for an overview of available CLI options.
In addition to index.js
, the following file names and extensions are also considered entry files:
index
,main
andcli
js
,mjs
,cjs
,jsx
,ts
,mts
,cts
andtsx
This means files like main.cjs
and src/cli.ts
are automatically added as entry files. Knip also looks for entry
files in other places:
- The
main
,bin
andexports
fields ofpackage.json
. - Plugins such as for Jest, Next.js, Svelte or Vitest etc. add entry files.
- The
scripts
in package.json and other scripts may provide entry files.
Knip does this for each workspace it finds. In a perfectly boring world where everything is according to defaults
you wouldn't even need a knip.json
file at all.
Larger projects tend to have more things customized, and therefore probably get more out of Knip with a configuration
file. Let's say you are using .ts
files exclusively and have all source files only in the src
directory:
{
"$schema": "https://unpkg.com/knip@2/schema.json",
"entry": ["src/index.ts"],
"project": ["src/**/*.ts"]
}
The entry
files target the starting point(s) to resolve the rest of the imported code. The project
files should
contain all files to match against the files resolved from the entry files, including potentially unused files.
Workspaces are handled out-of-the-box by Knip. Every workspace is part of the analysis.
Workspaces are sometimes also referred to as package-based monorepos, or as packages in a monorepo. Knip uses the term
workspace exclusively to indicate a directory that has a package.json
.
Here's an example knip.json
configuration with some custom entry
and project
patterns:
{
"workspaces": {
".": {
"entry": "scripts/*.js",
"project": "scripts/**/*.js"
},
"packages/*": {
"entry": "{index,cli}.ts",
"project": "**/*.ts"
},
"packages/my-lib": {
"entry": "main.js"
}
}
}
It might be useful to run Knip first with no or little configuration to see where it needs custom entry
and/or
project
files. Each workspace has the same default configuration.
The root workspace is named "."
under workspaces
(like in the example).
Knip supports workspaces as defined in three possible locations:
- In the
workspaces
array inpackage.json
(npm, Yarn, Lerna). - In the
packages
array inpnpm-workspace.yaml
(pnpm). - In the
workspaces.packages
array inpackage.json
(legacy). - In the
workspaces
object in Knip configuration.
The workspaces
in Knip configuration not already defined in the root package.json
or pnpm-workspace.yaml
are
added. Knip requires a package.json
file in each workspace directory.
Here's some example output when running Knip in a workspace:
Use --debug
to get more verbose output.
Use --workspace [dir]
to analyze a single workspace (including its ancestors).
The ignore
, ignoreBinaries
and ignoreDependencies
options are available inside workspace configurations.
Plugins tell Knip where to look for configuration and entry files, and if necessary have a custom dependency finder. Knip plugins are automatically activated, you don't need to install or configure anything.
To explain what they do, here's a quick example .eslintrc.json
configuration file (for ESLint):
{
"extends": ["airbnb"],
"plugins": ["prettier"]
}
Knip's ESLint plugin reads .eslintrc.json
and will return eslint-config-airbnb
and eslint-plugin-prettier
from
this example to Knip, so it can tell you whether package.json
is out of sync. In a nutshell, this is how plugins work.
This is especially useful over time when such configuration files change (and they will)!
Knip contains a growing list of plugins:
- Angular
- Astro
- Ava
- Babel
- Capacitor
- Changesets
- Commitizen
- commitlint
- cspell
- Cypress
- Drizzle
- ESLint
- Gatsby
- GitHub Actions
- husky
- Jest
- Lefthook
- lint-staged
- markdownlint
- Mocha
- Next.js
- Node.js Test Runner
- npm-package-json-lint
- Nx
- nyc
- Playwright
- Playwright for components
- PostCSS
- Prettier
- Release It
- Remark
- Remix
- Rollup
- Semantic Release
- Sentry
- Storybook
- Stryker
- Stylelint
- Svelte
- Tailwind
- TypeDoc
- TypeScript
- Vite
- Vitest
- Webpack
Plugins are automatically activated. Each plugin is automatically enabled based on simple heuristics. Most of them check
whether one of a few dependencies is listed in package.json
. Once enabled, they add a set of config
files for
themselves and/or entry
files for Knip to analyze.
config
files are given to the plugin's dependency finderentry
files are given to Knip to include with the analysis of the rest of the source code
See each plugin's documentation for its default values.
In an Astro, Svelte or Vue project? Make sure to see compilers and add some extra configuration.
Plugins usually include config
files. They are handled by the plugin's custom dependency finder, which returns all
dependencies referenced in the files it is given. Knip handles the rest to determine which of those dependencies are
unused or missing.
Other configuration files use require
or import
statements to use dependencies, so they don't need special handing
and can be analyzed like any other source file. That's why these configuration files are also used as entry
files.
Is a plugin not behaving as expected? There are a few options to modify its behavior:
- Disable a plugin by setting its value to
false
(.e.g"webpack": false
) - Force-enable a plugin by setting its value to
true
- Override a plugin's
config
orentry
location, for example:
{
"mocha": {
"config": "config/mocha.config.js",
"entry": ["**/*.spec.js"]
}
}
It's rarely necessary to override the entry
patterns, since plugins also read custom test file patterns from the
tooling configuration (e.g. from the include
option for Vitest or testMatch
for Playwright).
Plugin configuration can be set on root and workspace level. If enabled on root level, it can be disabled on workspace
level by setting it to false
there, and vice versa.
Some repositories have a single package.json
, but consist of multiple projects with configuration files across the
repository (such as the Nx "intregrated repo" style). Let's assume some of these projects are apps and have their
own Cypress configuration and test files. In that case, we could configure the Cypress plugin like this:
{
"cypress": {
"entry": ["apps/**/cypress.config.ts", "apps/**/cypress/e2e/*.spec.ts"]
}
}
Getting false positives because a plugin is missing? Want to help out? Please read more at writing a plugin. This guide also contains more details if you want to learn more about plugins and why they are useful.
Compilers allow to include files that are not JavaScript or TypeScript in the process of finding unused or missing
dependencies (e.g. .mdx
, .vue
and .svelte
files).
This requires using a dynamic knip.js
or knip.ts
configuration file. Provide a compilers
object in the
configuration where each key represents the extension and the value is a function that takes the contents of these files
as input and returns JavaScript or TypeScript as output. Here is an example that compiles .mdx
files to JavaScript so
these files and their imports and exports become part of the analysis:
import { compileSync } from 'mdx-js/mdx';
export default {
compilers: {
mdx: compileSync,
},
};
Read Compilers for more details and examples.
There are a few ways to tell Knip to ignore certain files, binaries, dependencies and workspaces. Some examples:
{
"ignore": ["**/fixtures"],
"ignoreBinaries": ["zip", "docker-compose"],
"ignoreDependencies": ["hidden-package"],
"ignoreWorkspaces": ["packages/ignore", "packages/examples/**"]
}
These can also be configured per workspace (except for ignoreWorkspaces
).
Sometimes a file that's not an entry file has one or more exports that are public and should not be reported as unused.
Such variables and types can be marked with the JSDoc @public
tag:
/**
* Merge two objects.
*
* @public
*/
export const merge = function () {};
/** @public */
export const split = function () {};
Knip does not report public exports and types as unused.
In files with multiple exports, some of them might be used only internally. If these exports should not be reported,
there is a ignoreExportsUsedInFile
option available. With this option enabled, you don't need to mark everything
@public
separately and when something is no longer used internally, it will still be reported.
{
"ignoreExportsUsedInFile": true
}
In a more fine-grained manner, you can also ignore only specific issue types:
{
"ignoreExportsUsedInFile": {
"interface": true,
"type": true
}
}
When a repository is self-contained or private, you may want to include entry files when reporting unused exports:
knip --include-entry-exports
Knip will also report unused exports in entry source files and scripts (such as those referenced in package.json
). But
not in entry and configuration files from plugins, such as next.config.js
or src/routes/+page.svelte
.
Tools like TypeScript, Webpack and Babel support import aliases in various ways. Knip automatically includes
compilerOptions.paths
from the TypeScript configuration, but does not (yet) automatically find other types of import
aliases. They can be configured manually:
{
"$schema": "https://unpkg.com/knip@2/schema.json",
"paths": {
"@lib": ["./lib/index.ts"],
"@lib/*": ["./lib/*"]
}
}
Each workspace can also have its own paths
configured. Knip paths
follow the TypeScript semantics:
- Path values are an array of relative paths.
- Paths without an
*
are exact matches.
The default mode for Knip is holistic and targets all project code, including configuration files and tests. Test files usually import production files. This prevents the production files or their exports from being reported as unused, while sometimes both of them can be removed. This is why Knip has a "production mode".
To tell Knip what is production code, add an exclamation mark behind each pattern!
that is meant for production and
use the --production
flag. Here's an example:
{
"entry": ["src/index.ts!", "build/script.js"],
"project": ["src/**/*.ts!", "build/*.js"]
}
Here's what's included in production mode analysis:
- Only
entry
andproject
patterns suffixed with!
. - Only production
entry
file patterns exported by plugins (such as Next.js and Gatsby). - Only the
start
andpostinstall
scripts (e.g. not thetest
or other npm scripts inpackage.json
). - Only unused
exports
,nsExports
andclassMembers
are reported (nottypes
,nsTypes
,enumMembers
).
Additionally, the --strict
flag can be used to:
- Consider
dependencies
(notdevDependencies
) when finding unused or unlisted dependencies. - Include
peerDependencies
when finding unused or unlisted dependencies. - Ignore type-only imports (
import type {}
). - Verify each workspace is self-contained: have their own
dependencies
(and not use packages of ancestor workspaces).
In addition to the flags above, the --ignore-internal
flag can be used to ignore exports tagged with @internal
. This
can be useful in production mode, since @internal
exports might be only imported from non-production code.
Plugins also have this distinction. For instance, Next.js entry files for pages (pages/**/*.tsx
) and Remix routes
(app/routes/**/*.tsx
) represent production code, while Jest and Storybook entry files (e.g. *.spec.ts
or
*.stories.js
) do not. All of this is handled automatically by Knip and its plugins.
Here's an example run using the default reporter:
This example shows more output related to unused and unlisted dependencies:
The report contains the following types of issues:
Title | Description | Key |
---|---|---|
Unused files | unable to find references to this file | files |
Unused dependencies | unable to find references to this dependency | dependencies |
Unused devDependencies | unable to find references to this devDependency | dependencies |
Unlisted dependencies | used dependencies not listed in package.json | unlisted |
Unlisted binaries | binaries from deps not listed in package.json | binaries |
Unresolved imports | unable to resolve this (import) specifier | unresolved |
Unused exports | unable to find references to this export | exports |
Unused exports in namespaces | unable to find direct references to this export | nsExports |
Unused exported types | unable to find references to this exported type | types |
Unused exported types in namespaces | unable to find direct references to this export | nsTypes |
Unused exported enum members | unable to find references to this enum member | enumMembers |
Unused exported class members | unable to find references to this class member | classMembers |
Duplicate exports | the same thing is exported more than once | duplicates |
Note that devDependencies
is covered within a single key for all dependencies
. In strict production mode,
devDependencies
are not included.
When an issue type has zero issues, it is not shown.
Getting too many reported issues and false positives? Read more about handling issues.
Use rules or filters if you don't (yet) want all issue types Knip reports.
Use rules
in the configuration to customize the issue types that count towards the total error count, or to exclude
them altogether.
error
(default): printed, adds to total error count (similar to the--include
filter)warn
: printed in faded/grey color, does not add to error count (i.e. the exit code)off
: not printed, does not add to error count (similar to the--exclude
filter)
Example:
{
"rules": {
"files": "warn",
"classMembers": "off",
"duplicates": "off"
}
}
See reading the report for the list of issue types.
The rules are modeled after the ESLint rules
configuration, and could be extended in the future. For instance, to
apply filters or configurations only to a specific issue type.
You can --include
or --exclude
any of the reported issue types to slice & dice the report to your needs.
Alternatively, they can be added to the configuration (e.g. "exclude": ["dependencies"]
).
Use --include
to report only specific issue types (the following example commands do the same):
knip --include files --include dependencies
knip --include files,dependencies
Use --exclude
to ignore reports you're not interested in:
knip --include files --exclude classMembers,enumMembers
Use --dependencies
or --exports
as shortcuts to combine groups of related types.
See reading the report for the list of issue types.
Filters are meant to be used as command-line flags, rules allow for more fine-grained configuration.
- Rules are more fine-grained since they also have "warn".
- Rules could be extended in the future.
- Filters can be set in configuration and from CLI, rules only in configuration.
- Filters have two groups (
--dependencies
and--exports
), rules don't have any grouping.
Knip provides the following built-in reporters:
- codeowners
- compact
- json
- symbol (default)
When the provided built-in reporters are not sufficient, a custom reporter can be implemented.
Find more details in reporters and preprocessors.
Use preprocessers to modify the results before they're passed to the reporter(s).
Find more details in reporters and preprocessors.
This is the fun part! Knip, knip, knip ✂️
Tip: back up files or use an VCS like Git before deleting files or making changes. Run tests to verify results.
- Unused files can be removed.
- Unused dependencies can be removed from
package.json
. - Unlisted dependencies should be added to
package.json
. - Unresolved imports should be reviewed.
- Unused exports and types: remove the
export
keyword in front of unused exports. Then you can see whether the variable or type is used within the same file. If this is not the case, it can be removed. - Duplicate exports can be removed so they're exported only once.
Repeat the process to reveal new unused files and exports. It's so liberating to remove unused things!
Getting too many reported issues and false positives? Read more about handling issues describing potential causes for false positives, and how to handle them.
Knip takes the following JSDoc/TSDoc tags into account:
Tag | Description |
---|---|
@public |
Do not report this unused export, type or member |
@beta |
^^ Idem |
@internal |
Do not report this unused export in --production mode |
@alias |
Do not report this duplicate export |
$ npx knip --help
✂️ Find unused files, dependencies and exports in your JavaScript and TypeScript projects
Usage: knip [options]
Options:
-c, --config [file] Configuration file path (default: [.]knip.json[c], knip.js, knip.ts or package.json#knip)
-t, --tsConfig [file] TypeScript configuration path (default: tsconfig.json)
--production Analyze only production source files (e.g. no tests, devDependencies, exported types)
--strict Consider only direct dependencies of workspace (not devDependencies, not other workspaces)
--ignore-internal Ignore exports with tag @internal (JSDoc/TSDoc)
-W, --workspace [dir] Analyze a single workspace (default: analyze all configured workspaces)
--directory [dir] Run process from a different directory (default: cwd)
--no-gitignore Don't use .gitignore
--include Report only provided issue type(s), can be comma-separated or repeated (1)
--exclude Exclude provided issue type(s) from report, can be comma-separated or repeated (1)
--dependencies Shortcut for --include dependencies,unlisted,unresolved
--exports Shortcut for --include exports,nsExports,classMembers,types,nsTypes,enumMembers,duplicates
--include-entry-exports Include entry files when reporting unused exports
-n, --no-progress Don't show dynamic progress updates (automatically enabled in CI environments)
--preprocessor Preprocess the results before providing it to the reporter(s), can be repeated
--preprocessor-options Pass extra options to the preprocessor (as JSON string, see --reporter-options example)
--reporter Select reporter: symbols, compact, codeowners, json, can be repeated (default: symbols)
--reporter-options Pass extra options to the reporter (as JSON string, see example)
--no-config-hints Suppress configuration hints
--no-exit-code Always exit with code zero (0)
--max-issues Maximum number of issues before non-zero exit code (default: 0)
-d, --debug Show debug output
--debug-file-filter Filter for files in debug output (regex as string)
--performance Measure count and running time of expensive functions and display stats table
-h, --help Print this help text
-V, --version Print version
(1) Issue types: files, dependencies, unlisted, unresolved, exports, nsExports, classMembers, types, nsTypes, enumMembers, duplicates
Examples:
$ knip
$ knip --production
$ knip --workspace packages/client --include files,dependencies
$ knip -c ./config/knip.json --reporter compact
$ knip --reporter codeowners --reporter-options '{"path":".github/CODEOWNERS"}'
$ knip --debug --debug-file-filter '(specific|particular)-module'
More documentation and bug reports: https://github.com/webpro/knip
To increase performance in a large monorepo, check out Potential boost with --no-gitignore
.
This table is an ongoing comparison. Based on their docs (please report any mistakes):
Feature | knip | depcheck | unimported | ts-unused-exports | ts-prune |
---|---|---|---|---|---|
Unused files | ✅ | - | ✅ | - | - |
Unused dependencies | ✅ | ✅ | ✅ | - | - |
Unlisted dependencies | ✅ | ✅ | ✅ | - | - |
Plugins | ✅ | ✅ | ❌ | - | - |
Compilers | ✅ | - | - | - | - |
Unused exports | ✅ | - | - | ✅ | ✅ |
Unused class members | ✅ | - | - | - | - |
Unused enum members | ✅ | - | - | - | - |
Duplicate exports | ✅ | - | - | ❌ | ❌ |
Search namespaces | ✅ | - | - | ✅ | ❌ |
Custom reporters | ✅ | - | - | - | - |
JavaScript support | ✅ | ✅ | ✅ | - | - |
Configure entry files | ✅ | ❌ | ✅ | ❌ | ❌ |
Workspaces | ✅ | ❌ | ❌ | - | - |
ESLint plugin available | - | - | - | ✅ | - |
✅ = Supported, ❌ = Not supported, - = Out of scope
Below some similar commands to get another idea of what Knip does in comparison:
The following commands are similar:
depcheck
knip --dependencies
The following commands are similar:
unimported
knip --production --dependencies --include files
Also see production mode.
The following commands are similar:
ts-unused-exports
knip --include exports,types,nsExports,nsTypes
knip --exports # Adds unused enum and class members
The following commands are similar:
ts-prune
knip --include exports,types
knip --exports # Adds unused exports/types in namespaces and unused enum/class members
Many thanks to some of the early adopters of Knip:
- Block Protocol
- DeepmergeTS
- eslint-plugin-functional
- freeCodeCamp.org
- is-immutable-type
- IsaacScript
- Nuxt
- Owncast
- release-it
- Template TypeScript Node Package
- Tipi
- Discord: hang out in The Knip Barn
- Ask your questions in the Knip knowledge base (powered by OpenAI and 7-docs, experimental!)
- Smashing Magazine: Knip: An Automated Tool For Finding Unused Files, Exports, And Dependencies
- Effective TypeScript: Recommendation Update: ✂️ Use knip to detect dead code and types
- Josh Goldberg: Speeding Up Centered Part 4: Unused Code Bloat
Knip is Dutch for a "cut". A Dutch expression is "ergens geknipt voor zijn", which means to be perfectly suited for the job. I'm motivated to make Knip perfectly suited for the job of cutting projects to perfection! ✂️
As listed above, there are already some great packages available if you want to find unused dependencies OR unused exports. I love the Unix philosophy ("do one thing well"). But looking at what Knip does, I believe it's efficient to handle multiple concerns in a single tool. When building a dependency graph of the project, an abstract syntax tree for each file, and traversing all of this, why not collect the various issues in one go?
Special thanks to the wonderful people who have contributed to this project: