knip Documentation

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

RAW Doc

Technical Documentation: webpro-nl/knip

ℹ️ Provenance: Multi-Tier Ingestion · CodeWiki Reference

Overview (`packages/create-config/README.md`)

@knip/create-config

Add Knip to a repository.

shell
npm init @knip/config

This adds Knip and its peer dependencies to devDependencies.

Example result:

json
{
  "name": "my-package",
  "scripts": {
    "knip": "knip"
  },
  "devDependencies": {
    "@types/node": "^20.14.8",
    "knip": "^5.30.1",
    "typescript": "^5.5.4"
  }
}

Overview (`packages/docs/README.md`)

knip.dev

The source of knip.dev.


Overview (`packages/knip/README.md`)

Knip finds and fixes unused dependencies, exports and files in your
JavaScript and TypeScript projects. Less code and dependencies lead to improved
performance, less maintenance and easier refactorings.

Contributors

Special thanks to the wonderful people who have contributed to Knip!

Knip

/'knɪp/ means "(to) cut" and is pronounced with a hard "K" 🇳🇱

License

Knip is free and open-source software licensed under the ISC License.

Parts of Knip have been inspired by and/or partially copy code from the
following projects:


Overview (`packages/language-server/README.md`)

Knip Language Server

Knip

The Knip Language Server is powered by Knip: Find unused files, dependencies,
and exports in your JavaScript/TypeScript projects.

Contents

Configuration

Latest version of available settings: types.d.ts

Diagnostics

Diagnostics should work out of the box.

Most Knip issue types are translated to Diagnostic items with a
DiagnosticSeverity and emitted using this.connection.sendDiagnostics(). Also
see diagnostics.js for details.

Code Actions

Code actions should work out of the box.

Some issues/diagnostics have code actions available. Also see
code-actions.js for details.

File Descriptor

Clients request a file descriptor to get available data for a document by
sending the REQUEST_FILE_NODE request, in short:

ts
const file = await this.#client.sendRequest(REQUEST_FILE_NODE, {
  uri: editor.document.uri.toString(),
});

Type definition for File: session/types.ts

The file descriptor can be used to implement features like Annotations,
Export Hover, Imports and Exports.

Annotations

Annotations (aka "Code Lens" or "Inlay Hint") for exported identifiers can be
implemented using data from the file descriptor.

Example:

Export Hover

On hover of an export identifier, the file descriptor can be used to render
import locations for the exported identifier.

Optionally, code snippets can be searched for using the provided locations and
mixed into the rendered list.

Example:

Imports

The file descriptor can be used to display an overview of imports of a document
with direct links to their definition location.

Optionally, the client can implement:

  • Follow cursor between open document and highlight import in view
  • Show cyclic dependencies

Example:

Exports

The file descriptor can be used to display an overview of exports of a document
with direct links to their usage locations.

Optionally, the client can implement:

  • Follow cursor between open document and highlight export in view
  • Show contention: ambiguous or shadowed exports behind re-exports
  • Show contention: converged (diamond-shaped) re-export paths

Each File.contention[name].sites entry describes where contention occurs, its kind, and the originating bindings.

Example:

File Descriptor for package.json

Clients request the package.json file descriptor to get dependency usage data
by sending the REQUEST_PACKAGE_JSON request:

ts
const packageJson = await this.#client.sendRequest(REQUEST_PACKAGE_JSON);

Type definition for DependencyNodes: session/types.ts

Dependency Hover

On hover of a dependency name in package.json, the packageJson descriptor
can be used to render import locations for the dependency.

Example:

CLI

To run the language server directly from CLI:

sh
npx @knip/language-server

Add --node-ipc, --stdio, --socket or --pipe <name> to set
transport (default: --stdio).


Overview (`packages/mcp-server/README.md`)

Knip MCP Server

Knip

The Knip MCP Server is powered by Knip: Find unused files, dependencies, and
exports in your JavaScript/TypeScript projects.

VS Code Extension

Install the Knip VS Code Extension or Knip Open VSX Extension — it
comes with the MCP Server included.

MCP Server

Add to your MCP configuration:

jsonc
{
  // or "mcpServers"
  "servers": {
    "knip": {
      "command": "npx",
      "args": ["-y", "@knip/mcp"],
    },
  },
}

Prompts

  • knip-configure — Guided workflow to set up and optimize Knip configuration

Tools

  • knip-run — Run Knip, returns configuration hints and issues
  • knip-docs — Get Knip documentation by topic

Resources

All pages of the Knip documentation are available as MCP resources
(knip://docs/{topic}).


Overview (`packages/vscode-knip/README.md`)

Knip Editor Extension for VS Code/Open VSX

The Knip Editor Extension for VS Code/Open VSX is powered by Knip: Find unused
files, dependencies, and exports in your JavaScript/TypeScript projects.

What Is This?

The usual suspects like red squiggles for unused exports are there. What
really moves the needle for DX with Knip's module graph is navigation. A
completely unique way to view & fly through codebases for developers who care.
Connect the dots during development and refactors, while keeping things in
check. We're starting out with 3 key features:

  1. Hover over Export for import & usage locations
  2. Imports Tree View for direct links to implementations
  3. Exports Tree View for direct links to import & usage locations

Features

  • Diagnostics for unused files, dependencies, and exports
  • Hover on exports to see import locations
  • Tree views for imports and exports
  • Code actions to fix or ignore issues
  • CodeLens showing import counts for exports

MCP Tools

The extension provides MCP tools for coding agents:

  • knip-configure — Run Knip and configure
  • knip-docs — Get Knip documentation by topic

In other words, you can tell your coding agent to "configure knip" and it will
RTFM so you don't have to. Using a newer model results in an optimized
knip.json file and an uncluttered codebase.

Screenshots

Lint Findings

Lint Findings

Imports & Exports

hover

Contention

The extension shows circular dependencies and three kinds of export contention: ambiguous, shadowed, and converged.

Circular Dependencies

If an import is part of a circular dependency, Knip will display:

Circular Dependencies

Ambiguous

Different bindings compete for the same name, with no winner. Knip lists the competing bindings.

Ambiguous

Shadowed

An explicit export hides bindings from export *. Knip labels the site as "shadowing" and lists the winning and hidden bindings.

Shadowed

Converged

The same binding arrives through multiple re-export paths. Knip shows where those paths meet and the binding they share.

Converged

VS Code Extension Settings

VS Code Extension Settings


Overview (`templates/vscode-nested-root/README.md`)

vscode-nested-root — repro for PR #1667

The VS Code workspace folder (repository/) has no package.json. The
actual project lives one level down in repository/frontend/.

  • repository.code-workspace opens ./repository and sets
    "knip.cwd": "./frontend".

Expected

Knip root Result
repository/ (no knip.cwd, current main) ❌ ERROR: Unable to find package.json in the Knip Output channel
repository/frontend/ (with knip.cwd, PR #1667) ✅ runs; reports src/unused.ts (genuine unused file)

CLI check

sh
cd repository            && knip   # → ERROR: Unable to find package.json
cd repository/frontend && knip   # → src/unused.ts unused (correct)

Test in the editor

sh
packages/vscode-knip/scripts/dev-install.sh

Then open repository.code-workspace and watch View → Output → "Knip".


Overview (`templates/issue-reproduction/basic/README.md`)

Knip Minimal Reproduction

This is a template with only Knip (and TypeScript) pre-installed.

Add files and configuration to this project to create a minimal reproduction of
the issue:

  • Add source files
  • Add relevant TypeScript configuration to tsconfig.json
  • Add relevant Knip configuration to knip.json (or create knip.ts)
  • Install and double-check the latest versions of Knip and TypeScript

Use the terminal inside this project to run Knip from the root of the project
with npx knip.


Overview (`templates/issue-reproduction/monorepo/README.md`)

Knip Minimal Reproduction

This is a template with only Knip (and TypeScript) pre-installed.

Add files and configuration to this project to create a minimal reproduction of
the issue:

  • Add source files
  • Add relevant TypeScript configuration to tsconfig.json
  • Add relevant Knip configuration to knip.json (or create knip.ts)
  • Install and double-check the latest versions of Knip and TypeScript

Use the terminal inside this project to run Knip from the root of the project
with npx knip.


Overview (`templates/playground/basic/README.md`)

Knip Playground (basic)

Welcome to this Knip playground. There are issues on purpose in this codebase
below so Knip will report various types of issues.

Feel free to (fork this project and) play around!

Run npm run knip on this codebase in the terminal below and Knip will report
the following issues:

text
$ npm run knip
Unused files (1)
clutter.ts
Unlisted dependencies (1)
lodash  util.ts
Unused exports (1)
unusedFunction  unknown  util.ts:6:14
  • clutter.ts is an unused file, since it's not imported by any of the others
  • lodash is an unlisted dependency, because it's used in util.ts, but not
    listed in package.json
  • unusedFunction is exported from util.ts but it's not imported anywhere, so
    it's reported as an unused export.

Overview (`templates/playground/monorepo/README.md`)

Knip Playground (monorepo)

Welcome to this Knip playground. There are issues on purpose in this codebase
below so Knip will report various types of issues.

Feel free to (fork this project and) play around!

Run npm run knip on this codebase in the terminal below and Knip will report
the following issues:

text
$ npm run knip
Unused dependencies (1)
tinyglobby  package.json
Unlisted dependencies (1)
js-yaml  packages/shared/src/used-fn.ts
Unused exports (1)
unusedFunction  unknown  packages/shared/src/exports.ts:7:14
  • tinyglobby is unused in the root package.json. It's listed and used in the
    packages/shared workspace, but not referenced elsewhere in the root
    workspace.
  • js-yaml is used in packages/shared/src/used-fn.ts, but it's not listed as
    a dependency in packages/shared/package.json.
  • unusedFunction is exported from packages/shared/src/exports.ts but it's
    not imported anywhere.

Packages Docs Src Content Docs Index (`packages/docs/src/content/docs/index.mdx`)


title: Declutter your JavaScript & TypeScript projects
template: splash
hero:
title: Knip
tagline: Declutter your JavaScript & TypeScript projects
image:
file: ../../assets/logo.svg
actions:
- text: Let's Get Started
link: ./overview/getting-started
icon: right-arrow
variant: primary
- text: View on GitHub
link: https://github.com/webpro-nl/knip
icon: external
variant: minimal


import { Card, CardGrid } from '@astrojs/starlight/components';
import { YouTube } from '@astro-community/astro-embed-youtube';
import Posts from '../../components/Posts.astro';
import Sponsors from '../../components/Sponsors.astro';
import Projects from '../../components/Projects.astro';
import Contributors from '../../components/Contributors.astro';
import ContributorCount from '../../components/ContributorCount.astro';

:::section{.badges}

NPM Version NPM Downloads GitHub Repo stars

:::

:::div{.sponsor}

Sponsor Me / Hire Me

:::

:a[Sponsored by:]{.sponsors-intro href="/sponsors"}

Knip finds and fixes unused dependencies, exports and files. Use it to prune your codebase to perfection. Advanced analysis starting from fine-grained entry points based on the actual frameworks and tooling in (mono)repos for accurate and actionable results. [86M downloads/month][8] and [over 21.000 public projects][9] are using Knip! Knip comes with [190+ plugins][10] for tools and frameworks like Astro, Cypress, ESLint, Jest, GitHub Actions, Next.js, Nx, Remix, Storybook, Svelte, Vite, Vitest and Webpack. Try Knip on the [playground][11]. Need help? [Start troubleshooting][12].

Trusted by the world's best software teams

:::section{.projects}

:::

Introduction video

James Shopland has a great introduction and overview of Knip (thanks
James!):

Created by awesome contributors

Special thanks to the wonderful people who have contributed to this project:

Articles about Knip

A curated selection of articles about Knip, most recent first:

Don't just take our word for it

Read More


Packages Docs Src Content Docs Overview Getting Started (`packages/docs/src/content/docs/overview/getting-started.mdx`)


title: Getting Started

import { Tabs, TabItem } from '@astrojs/starlight/components';

Requirements

Knip v6 requires at least Node.js v20.19.0. Or Bun.

Want to try Knip without installation? Visit the playground.

Installation

The easiest and recommended way to install Knip:

```shell npm init @knip/config ``` ```shell pnpm create @knip/config ``` ```shell bun create @knip/config ``` ```shell yarn create @knip/config ```

Run

Now you can run Knip to lint your project:

```shell npm run knip ``` ```shell pnpm knip ``` ```shell bun knip ``` ```shell yarn knip ```

Knip will lint your project and report unused dependencies, exports and files.

The next step is to learn more about how Knip works or go straight to your
first cleanup
.

:::tip

Try the Knip Editor Extension! Let your coding agent use the built-in MCP
Server and create a custom knip.json for you.

:::

Manual

Alternatively, manually install Knip using your package manager:

```shell npm install -D knip typescript @types/node ``` ```shell pnpm add -D knip typescript @types/node ``` ```shell bun add -D knip typescript @types/node ``` ```shell yarn add -D knip typescript @types/node ```

Knip uses typescript and @types/node as peer dependencies to increase
compatibility with your project. No worries, they're probably in your
node_modules already.

Then add a knip script to your package.json:

json title="package.json"
{
  "name": "my-project",
  "scripts": {
    "knip": "knip"
  }
}

Without installation

To run Knip without adding it to your project:

```shell npx knip ``` ```shell pnpm dlx knip ``` ```shell bunx knip ```

In this scenario typescript and @types/node are expected to be installed
already.


Packages Docs Src Content Docs Writing A Plugin Index (`packages/docs/src/content/docs/writing-a-plugin/index.md`)


title: Writing A Plugin

Plugins provide Knip with entry files and dependencies it would be unable to
find otherwise. Plugins always do at least one of the following:

  1. Define entry file patterns
  2. Find dependencies in configuration files

Knip v5.1.0 introduced a new plugin API, which makes them a breeze to write and
maintain.

:::tip[The new plugin API]

Easy things should be easy, and complex things possible.

:::

This tutorial walks through example plugins so you'll be ready to write your
own! The following examples demonstrate the elements a plugin can implement.

There's a handy command available to easily create a new plugin and get
started right away.

Example 1: entry

Let's dive right in. Here's the entire source code of the Tailwind plugin:

ts
import type { IsPluginEnabled, Plugin } from '../../types/config.js';
import { hasDependency } from '../../util/plugin.js';

const title = 'Tailwind';

const enablers = ['tailwindcss'];

const isEnabled: IsPluginEnabled = ({ dependencies }) =>
  hasDependency(dependencies, enablers);

const entry = ['tailwind.config.{js,cjs,mjs,ts}'];

const plugin: Plugin = {
  title,
  enablers,
  isEnabled,
  entry,
};

export default plugin;

Yes, that's the entire plugin! Let's go over each item one by one:

1. `title`

The title of the plugin displayed in the list of plugins and in debug
output.

2. `enablers`

An array of strings to match one or more dependencies in package.json so the
isEnabled function can determine whether the plugin should be enabled or not.
Regular expressions are allowed as well.

3. `isEnabled`

This function checks whether a match is found in the dependencies or
devDependencies in package.json. The plugin is enabled if the dependency is
listed in package.json.

This function can be kept straightforward with the hasDependency helper.

4. `entry`

This plugin exports entry file patterns. This means that if the Tailwind
plugin is enabled, then tailwind.config.* files are added as entry files. A
Tailwind configuration file does not contain anything particular, so adding it
as an entry to treat it as a regular source file is enough.

The next example shows how to handle a tool that has its own particular
configuration object.

Example 2: config

Here's the full source code of the nyc plugin:

ts
import { toDeferResolve } from '../../util/input.js';
import { hasDependency } from '../../util/plugin.js';
import type { NycConfig } from './types.js';
import type {
  IsPluginEnabled,
  Plugin,
  ResolveConfig,
} from '../../types/config.js';

const title = 'nyc';

const enablers = ['nyc'];

const isEnabled: IsPluginEnabled = ({ dependencies }) =>
  hasDependency(dependencies, enablers);

const config = [
  '.nycrc',
  '.nycrc.{json,yml,yaml}',
  'nyc.config.js',
  'package.json',
];

const resolveConfig: ResolveConfig<NycConfig> = config => {
  const extend = config?.extends ?? [];
  const requires = config?.require ?? [];
  return [extend, requires].flat().map(id => toDeferResolve(id));
};

const plugin: Plugin = {
  title,
  enablers,
  isEnabled,
  config,
  resolveConfig,
};

export default plugin;

Here's an example config file that will be handled by this plugin:

json title=".nycrc.json"
{
  "extends": "@istanbuljs/nyc-config-typescript",
  "check-coverage": true
}

Compared to the first example, this plugin has two new variables:

5. `config`

The config array contains all possible locations of the config file for the
tool. Knip loads matching files and passes the results (i.e. its default export)
into the resolveConfig function:

6. `resolveConfig`

This function receives the exported value of the config file, and executes the
resolveConfig function with this object. The plugin should return the entry
paths and dependencies referenced in this object.

Knip supports JSON, YAML, TOML, JavaScript and TypeScript config files. Files
without an extension are provided as plain text strings.

:::tip[Should I implement resolveConfig?]

You should implement resolveConfig if any of these are true:

  • The config file contains one or more options that represent entry
    points
  • The config file references dependencies by strings (not import statements)

:::

Example 3: entry paths

7. entry and production

Some tools operate mostly on entry files, some examples:

  • Mocha looks for test files at test/*.{js,cjs,mjs}
  • Storybook looks for stories at *.stories.@(mdx|js|jsx|tsx)

And some of those tools allow to configure those locations and patterns in
configuration files, such as next.config.js or vite.config.ts. If that's the
case we can define resolveConfig in our plugin to take this from the
configuration object and return it to Knip:

Here's an example from the Mocha plugin:

ts
const entry = ['**/test/*.{js,cjs,mjs}'];

const resolveConfig: ResolveConfig<MochaConfig> = localConfig => {
  const entryPatterns = localConfig.spec ? [localConfig.spec].flat() : entry;
  return entryPatterns.map(id => toEntry(id));
};

export default {
  entry,
  resolveConfig,
};

With Mocha, you can configure spec file patterns. The result of implementing
resolveConfig is that users don't need to duplicate this configuration in both
the tool (e.g. Mocha) and Knip.

Use production entries to target source files that represent production code.

:::tip

Regardless of the presence of resolveConfig, add entry and production to
the default export so they will be displayed in the plugin's documentation as
default values.

:::

Example 4: Use the AST directly

If the resolveConfig function is implemented, Knip loads the configuration
file and passes the default-exported object to this plugin function. However,
that object might then not contain the information we need.

Here's an example astro.config.ts configuration file with a Starlight
integration:

ts
import starlight from '@astrojs/starlight';
import { defineConfig } from 'astro/config';

export default defineConfig({
  integrations: [
    starlight({
      components: {
        Head: './src/components/Head.astro',
        Footer: './src/components/Footer.astro',
      },
    }),
  ],
});

With Starlight, components can be defined to override the default internal ones.
They're not otherwise referenced in your source code, so you'd have to manually
add them as entry files (Knip itself did this).

In the Astro plugin, there's no way to access this object containing
components to add the component files as entry files if we were to try:

ts
const resolveConfig: ResolveConfig<AstroConfig> = async config => {
  console.log(config); //  ¯\_(ツ)_/¯
};

This is why plugins can implement the resolveFromAST function.

8. resolveFromAST

Let's take a look at the Astro plugin implementation. This example assumes some
familiarity with Abstract Syntax Trees (AST). Knip provides AST helpers to make
implementing plugins more fun and a little less tedious.

Anyway, let's dive in. Here's how we're adding the Starlight components paths
to the default production file patterns:

ts
import type { Program } from 'oxc-parser';
import {
  findCallArg,
  getDefaultImportName,
  getImportMap,
  getPropertyValues,
} from '../../typescript/ast-helpers.ts';

const title = 'Astro';

const production = [
  'src/pages/**/*.{astro,mdx,js,ts}',
  'src/content/**/*.mdx',
  'src/middleware.{js,ts}',
  'src/actions/index.{js,ts}',
];

const getComponentPaths = (program: Program) => {
  const importMap = getImportMap(program);
  const importName = getDefaultImportName(importMap, '@astrojs/starlight');
  if (!importName) return new Set<string>();
  const starlightConfig = findCallArg(program, importName);
  return getPropertyValues(starlightConfig, 'components');
};

const resolveFromAST: ResolveFromAST = (program: Program) => {
  const componentPaths = getComponentPaths(program);
  return [...production, ...componentPaths].map(id => toProductionEntry(id));
};

const plugin: Plugin = {
  title,
  production,
  resolveFromAST,
};

export default plugin;

None Of The Above

9. `resolve`

If there is no configuration file to parse or there is a need for customization,
use resolve:

ts
const resolve: Resolve = async options => {
  return toDependency('troublesome', { optional: true });
};

Inputs

You may have noticed functions like toDeferResolve and toEntry. They're a
way for plugins to tell what they've found and how Knip should handle those. The
more precision a plugin can provide here, the better results and performance
will be.

Find all the details over at Writing A Plugin → Inputs.

Argument Parsing

As part of the script parser, Knip parses command-line arguments. Plugins
can implement the arg object to add custom argument parsing tailored to the
tool.

Read more in Writing A Plugin → Argument Parsing.

Create a new plugin

The easiest way to create a new plugin is to use the create-plugin script:

sh
cd packages/knip
pnpm create-plugin --name tool

This adds source and test files and fixtures to get you started. Keep fixtures
tidy, remove clutter that isn't required to test the plugin properly. E.g. using
a starterkit, script or wizard often creates more than what we need here.

The script adds the plugin to the JSON Schema and type definitions.

Run the test for your new plugin using one of the following commands:

sh
node --test test/plugins/tool.test.ts
bun test test/plugins/tool.test.ts

You're ready to implement and submit a new Knip plugin! 🆕 🎉

Wrapping Up

Feel free to check out the implementation of other similar plugins, and borrow
ideas and code from those!

The documentation website takes care of generating the plugin list and the
individual plugin pages
from the exported plugin values.

Thanks for reading. If you have been following this guide to create a new
plugin, this might be the right time to open a pull request!


Packages Docs Package.Json (`packages/docs/package.json`)

{
"name": "@knip/docs",
"version": "0.0.0",
"private": true,
"description": "Documentation for Knip, hosted at knip.dev",
"homepage": "https://knip.dev",
"repository": {
"type": "git",
"url": "github:webpro-nl/knip",
"directory": "packages/docs"
},
"type": "module",
"scripts": {
"prebuild": "node ./scripts/generate-plugin-docs.ts",
"build": "astro check && astro build",
"postbuild": "[ "$CF_PAGES" = "1" ] && find dist -mindepth 2 -type f -name 'index.html' -exec bash -c 'f="$1"; d=$(dirname "$f"); bn=$(basename "$d"); mv -v "$f" "$d/../$bn.html"' _ {} \; && find dist -mindepth 1 -type d -empty -print -delete || true",
"dev": "astro dev",
"remark": "remark README.md 'src/content/docs//*.md' --verbose -o && mdxlint 'src/content/docs//*.mdx' --verbose -o"
},
"dependencies": {
"@astro-community/astro-embed-youtube": "0.5.10",
"@astrojs/starlight": "0.42.6",
"@octokit/graphql": "^9.0.5",
"astro": "7.3.8",
"sharp": "0.35.5"
},
"devDependencies": {
"@astrojs/check": "0.9.10",
"@astrojs/markdown-satteri": "0.4.3",
"picocolors": "^1.1.1",
"satteri": "0.10.5",
"starlight-blog": "^0.30.2",
"typescript": "6.0.3"
},
"engines": {
"node": ">=22.12.0"
}
}


Packages Docs Mock Contributors.Json (`packages/docs/mock/contributors.json`)

[
{
"login": "webpro",
"id": 456426,
"node_id": "MDQ6VXNlcjQ1NjQyNg==",
"avatar_url": "https://avatars.githubusercontent.com/u/456426?v=4",
"gravatar_id": "",
"url": "https://api.github.com/users/webpro",
"html_url": "https://github.com/webpro",
"followers_url": "https://api.github.com/users/webpro/followers",
"following_url": "https://api.github.com/users/webpro/following{/other_user}",
"gists_url": "https://api.github.com/users/webpro/gists{/gist_id}",
"starred_url": "https://api.github.com/users/webpro/starred{/owner}{/repo}",
"subscriptions_url": "https://api.github.com/users/webpro/subscriptions",
"organizations_url": "https://api.github.com/users/webpro/orgs",
"repos_url": "https://api.github.com/users/webpro/repos",
"events_url": "https://api.github.com/users/webpro/events{/privacy}",
"received_events_url": "https://api.github.com/users/webpro/received_events",
"type": "User",
"site_admin": false,
"contributions": 1282
}
]


Packages Docs Public Manifest.Json (`packages/docs/public/manifest.json`)

{
"name": "Knip",
"short_name": "Knip",
"icons": [
{
"src": "/favicon.svg",
"type": "image/svg+xml",
"sizes": "512x512"
},
{
"src": "/icon-192x192.png",
"sizes": "192x192",
"type": "image/png"
},
{
"src": "/icon-512x512.png",
"sizes": "512x512",
"type": "image/png"
}
],
"theme_color": "#fbfbfb",
"background_color": "#f56e0f",
"display": "standalone",
"scope": "/",
"start_url": "/overview/getting-started/"
}


Packages Docs Public Robots (`packages/docs/public/robots.txt`)

User-agent: *
Allow: /

Sitemap: https://knip.dev/sitemap.txt


Packages Docs Src Assets Testimonials.Json (`packages/docs/src/assets/testimonials.json`)

{
"data": [
{
"entities": {
"urls": []
},
"id": "2061954856638967917",
"author_id": "5",
"created_at": "2026-06-02T23:35:21.000Z",
"text": "Knip is still my all time favorite piece of software."
},
{
"entities": {
"urls": []
},
"id": "2061775207518343569",
"author_id": "20",
"created_at": "2026-06-02T11:41:30.000Z",
"text": "adding knip to our CI after deleting like 100k LOC of dead code was a game changer"
},
{
"entities": {
"urls": []
},
"id": "3mn5ccucpvk2z",
"author_id": "13",
"created_at": "2026-05-31T09:54:50.000Z",
"text": "Knip is essential tooling."
},
{
"entities": {
"urls": []
},
"id": "2059921462891622770",
"author_id": "21",
"created_at": "2026-05-28T08:55:22.000Z",
"text": "Please dont ask your ai agent to find unused, dead code and remove them. Instead for js/ts projects install knip and see the magic it does. Save tokens🙏😂"
},
{
"entities": {
"mentions": [
{
"username": "webpro.nl"
}
],
"urls": []
},
"id": "3mgkwe4digs2l",
"author_id": "19",
"created_at": "2026-03-25T12:00:00.000Z",
"text": "Two things about knip.dev I don't get:\n- how did we live without it for so long\n- how did @webpro.nl NAIL the timing? Gave it to us right when AI started to produce left-overs in our codes faster than we ever could."
},
{
"entities": {
"urls": [
{
"url": "cdn.bsky.app/img/dummy-02",
"display_url": "cdn.bsky.app/img/julien.deniau.me",
"media_key": "4"
}
]
},
"id": "3mdrubg3y5c2z",
"author_id": "11",
"created_at": "2026-02-01T07:57:00.000Z",
"attachments": {},
"text": "Done on a mono-repo in two times.\nWhat's impressive is that there were no false positive!\ncdn.bsky.app/img/dummy-02"
},
{
"entities": {
"urls": [
{
"url": "https://t.co/EwIeRiHaXf",
"display_url": "pic.x.com/EwIeRiHaXf",
"media_key": "6"
}
]
},
"id": "2017122504914161979",
"author_id": "16",
"created_at": "2026-01-30T06:27:00.000Z",
"text": "Good morning 🌄\nA tool I discovered lately.\nIf you care about clean codebases, this one's a gem 💎"
},
{
"entities": {
"urls": []
},
"id": "3mcky5ps3r22p",
"author_id": "12",
"created_at": "2026-01-16T20:53:09.000Z",
"text": "Knip is a cracking-looking open source tool for trimming unused JavaScript."
},
{
"entities": {
"urls": [
{
"url": "cdn.bsky.app/img/dummy-03",
"display_url": "cdn.bsky.app/img/patak.dev",
"media_key": "5"
}
]
},
"id": "3mcjtmv35wc2y",
"author_id": "15",
"created_at": "2026-01-16T09:59:29.000Z",
"attachments": {},
"text": "From the report, we could improve the situation if we stop sending unused JavaScript. @webpro.nl is doing an incredible job growing the usage of knip.dev, and that should hopefully make a dent in the future.\ncdn.bsky.app/img/dummy-03"
},
{
"entities": {
"urls": [
{
"url": "https://knip.dev/blog/for-editors-and-agents",
"expanded_url": "https://knip.dev/blog/for-editors-and-agents",
"display_url": "knip.dev/blog/for-edi..."
}
]
},
"id": "3mby3zkzhfs2t",
"author_id": "13",
"created_at": "2026-01-09T08:41:49.000Z",
"text": "The VSCode/Cursor extension is ace!\nAnd you can use the Knip MCP to create the config file.\nIt works for Svelte as well.\nKnip is really really good 🙏"
},
{
"entities": {
"urls": [
{
"url": "https://t.co/6rcslWBqIV",
"display_url": "pic.x.com/6rcslWBqIV",
"media_key": "6"
}
]
},
"id": "2007481125610914217",
"author_id": "17",
"created_at": "2026-01-03T15:56:00.000Z",
"attachments": { "media_keys": ["6"] },
"text": "This is both embarrassing and satisfying after running @Knip https://t.co/6rcslWBqIV"
},
{
"entities": {
"urls": [
{
"url": "https://t.co/hneEWMqdnm",
"expanded_url": "https://knip.dev",
"display_url": "Knip.dev"
}
]
},
"id": "2007221517562904675",
"author_id": "18",
"created_at": "2026-01-02T23:44:00.000Z",
"text": "You can't spell Opus without https://t.co/hneEWMqdnm"
},
{
"entities": {
"urls": []
},
"id": "3mbggqrihuk2r",
"author_id": "14",
"created_at": "2026-01-02T08:05:50.000Z",
"text": "Knip is amazing and you should be using it."
},
{
"entities": {
"urls": []
},
"id": "3lvwax6bemc22",
"author_id": "22",
"created_at": "2025-08-08T22:02:18.000Z",
"text": "Just used npx knip to find my unused files, components, and packages and 😱! So cool. \n\nHow have I not come across it before?"
},
{
"entities": {
"urls": []
},
"id": "1932778410691949034",
"author_id": "10",
"created_at": "2025-06-11T14:34:00.000Z",
"text": ""Thanks Knip!"\neveryday,\nevery team member,\nliterally..\nknip.dev 🏅"
},
{
"entities": {
"urls": []
},
"url": "https://github.com/webpro-nl/knip/issues/1120#issuecomment-2943978262",
"author_id": "9",
"created_at": "2025-06-05T14:00:00.000Z",
"text": "Thank you so much for this incredible piece of software! Knip is one of the most impressive tools I've seen in my career."
},
{
"entities": {
"urls": [
{
"url": "https://github.com/webpro-nl/knip/pull/1022",
"expanded_url": "https://github.com/webpro-nl/knip/pull/1022",
"display_url": "https://github.com/webpro-nl/knip/pull/1022",
"title": "feat: add create-typescript-app plugin",
"description": "feat: add create-typescript-app plugin",
"unwound_url": "https://github.com/webpro-nl/knip/pull/1022"
}
]
},
"id": "3lmhgsj2n622o",
"author_id": "7",
"created_at": "2025-04-10T13:50:00.000Z",
"attachments": {},
"text": "Got my first plugin merged into Knip by @webpro.nl today! ☺️\n\nThe contribution experience was fantastic: the docs were super clear, the code was straightforward to navigate, and the helper script to generate a new plugin got me 90% of the way there. Awesome. Thanks Lars!\n\nhttps://github.com/webpro-nl/knip/pull/1022"
},
{
"entities": {
"urls": []
},
"id": "1904580358307250634",
"author_id": "6",
"created_at": "2025-03-25T18:05:00.000Z",
"attachments": {},
"text": "All of the TanStack monorepos make use of knip.dev\n\nIt's like eslint for detecting unused dependencies or even your own files/modules."
},
{
"entities": {
"urls": [
{
"url": "cdn.bsky.app/img/dummy-01",
"display_url": "cdn.bsky.app/img/opw3iapscc55nlgrx6q2yjah",
"media_key": "2"
}
]
},
"id": "3ll5ioywml223",
"author_id": "8",
"created_at": "2025-03-24T20:32:00.000Z",
"attachments": {},
"text": "The joy of adding knip.dev (by @webpro.nl) on a codebase! Right in time for spring cleaning 🧹🍃\ncdn.bsky.app/img/dummy-01"
},
{
"entities": {
"urls": []
},
"id": "1874692207472726401",
"author_id": "5",
"created_at": "2025-01-02T06:40:00.000Z",
"attachments": {},
"text": "Knip helped us delete ~300k lines of unused code at Vercel."
},
{
"entities": {
"urls": [
{
"url": "https://t.co/kA81p5gkmP",
"expanded_url": "https://www.npmx.dev/package/knip",
"display_url": "npmx.dev/package/knip",
"title": "Knip",
"description": "Find unused dependencies, exports and files in your TypeScript and JavaScript projects.",
"unwound_url": "https://www.npmx.dev/package/knip"
}
]
},
"id": "1850975288362221628",
"author_id": "5",
"created_at": "2024-10-28T19:57:00.000Z",
"attachments": {},
"text": "Knip may be the greatest tool ever conceived.\nhttps://t.co/kA81p5gkmP"
},
{
"entities": {
"urls": []
},
"id": "3k3obqi65oy2p",
"author_id": "4",
"created_at": "2023-07-29T17:33:00.000Z",
"attachments": {},
"text": "knip is the best code maintenance tool I've used in my 20 years of development, just an absolute work of art"
},
{
"entities": {
"urls": []
},
"id": "1808672067900092505",
"author_id": "3",
"created_at": "2024-07-04T03:19:00.000Z",
"attachments": {},
"text": "knip is great.\n\nA great example of stuff just working. It detects your environment so well and its defaults are almost too good because I didn’t realise it was doing everything automatically."
},
{
"entities": {
"urls": []
},
"id": "1808808208229707835",
"author_id": "3",
"created_at": "2024-07-04T12:20:00.000Z",
"attachments": {},
"text": "Then was like “this is how tooling should be, this has taken me an hour not a day and it’s faster and more accurate and more future proof”\n\nFive stars ⭐️⭐️⭐️⭐️⭐️"
},
{
"entities": {
"urls": []
},
"id": "1803013555907698921",
"author_id": "2",
"created_at": "2024-06-18T12:35:00.000Z",
"attachments": {},
"text": "Crazy useful tool. Thought it would be a nightmare to setup in my Next.js+Workers repo but it has crazy good automatic detection for code dependencies. In short, Knip creates a graph of code use and highlights dead code for you to snip (knip)."
},
{
"entities": {
"urls": []
},
"id": "1793913676896031033",
"author_id": "44217212",
"created_at": "2024-05-24T09:55:00.000Z",
"attachments": {},
"text": "Lars is singlehandedly building the best tool to keep your code clean and your dependencies minimal. And he's so engaging and helpful. Can't imagine going back to not using knip 👏"
},
{
"entities": {
"urls": [
{
"url": "https://t.co/dummy-01",
"display_url": "pic.twitter.com/uOuO1ch5ij",
"media_key": "1"
}
]
},
"id": "1745972825490604506",
"author_id": "1",
"created_at": "2024-01-13T01:55:00.000Z",
"attachments": { "media_keys": ["1"] },
"text": "warning: do not use a tool like knip.dev + code search to delete a ton of unused code. It works too well.\n\nI just did this in https://github.com/sourcegraph/cody/pull/2705, and my boss said if I end the week with net negative lines of code committed, I'm in deep trouble. https://t.co/dummy-01"
},
{
"entities": {
"mentions": [
{
"username": "argos_ci",
"id": "1554000987022663681"
},
{
"username": "webprolific",
"id": "218833730"
}
],
"annotations": [
{
"probability": 0.6699,
"type": "Other",
"normalized_text": "Prettier"
},
{
"probability": 0.7586,
"type": "Other",
"normalized_text": "ESLint"
}
],
"urls": [
{
"url": "https://t.co/uOuO1ch5ij",
"expanded_url": "https://twitter.com/gregberge_/status/1730180003453927560/photo/1",
"display_url": "pic.twitter.com/uOuO1ch5ij",
"media_key": "3_1730179997682552832"
}
]
},
"author_id": "22918124",
"text": "Ran knip.dev in @argos_ci, and boom 💥! Dead code detected instantly. With a good config, it can run on CI to keep projects clean. Installing this tool should be a no-brainer, like Prettier or ESLint.\n\nKudos, @webprolific! 👏 https://t.co/uOuO1ch5ij",

text
"id": "1730180003453927560",
  "created_at": "2023-11-30T11:00:32.000Z",
  "edit_history_tweet_ids": ["1730180003453927560"],
  "attachments": { "media_keys": ["3_1730179997682552832"] }
},
{
  "note_tweet": {
    "text": "Problem: \n🚫 Your project has unused files.\n🚫 It has unused npm dependencies.\n🚫 It has unused TypeScript exports.\n\nBut you haven't noticed, because these things are hard to spot.\n\nSolution: knip\n\nI just used knip to find and resolve dozens of issues.\n\nhttps://t.co/QmN3sNlmbm",
    "entities": {
      "urls": [
        {
          "url": "https://t.co/QmN3sNlmbm",
          "expanded_url": "https://github.com/webpro-nl/knip",
          "display_url": "github.com/webpro-nl/knip"
        }
      ]
    }
  },
  "entities": {
    "annotations": [
      {
        "probability": 0.8569,
        "type": "Other",
        "normalized_text": "TypeScript"
      }
    ],
    "urls": [
      {
        "url": "https://t.co/grbeTqMtt6",
        "expanded_url": "https://github.com/webpro-nl/knip",
        "display_url": "github.com/webpro-nl/knip",
        "images": [
          {
            "url": "https://pbs.twimg.com/news_img/1729485824314548224/0xZughNs?format=png&name=orig",
            "width": 1280,
            "height": 640
          },
          {
            "url": "https://pbs.twimg.com/news_img/1729485824314548224/0xZughNs?format=png&name=150x150",
            "width": 150,
            "height": 150
          }
        ],
        "title": "GitHub - webpro-nl/knip: ✂️ Find unused dependencies, exports and files in your JavaScript and TypeScript projects. Knip it before you ship it!",
        "description": "✂️ Find unused dependencies, exports and files in your JavaScript and TypeScript projects. Knip it before you ship it! - GitHub - webpro-nl/knip: ✂️ Find unused dependencies, exports and files in yo...",
        "unwound_url": "https://github.com/webpro-nl/knip"
      },
      {
        "url": "https://t.co/qaOXBlD3eo",
        "expanded_url": "https://twitter.com/i/web/status/1691460974518353920",
        "display_url": "twitter.com/i/web/status/1…"
      }
    ]
  },
  "author_id": "19268321",
  "text": "Problem: \n🚫 Your project has unused files.\n🚫 It has unused npm dependencies.\n🚫 It has unused TypeScript exports.\n\nBut you haven't noticed, because these things are hard to spot.\n\nSolution: knip\n\nI just used knip to find and resolve dozens of issues.\n\nhttps://t.co/grbeTqMtt6 https://t.co/qaOXBlD3eo",

  "id": "1691460974518353920",
  "created_at": "2023-08-15T14:44:56.000Z",
  "edit_history_tweet_ids": ["1691460974518353920"]
},
{
  "entities": {
    "mentions": [
      {
        "username": "webprolific",
        "id": "218833730"
      }
    ],
    "annotations": [
      {
        "probability": 0.6322,
        "type": "Other",
        "normalized_text": "Knip"
      }
    ]
  },
  "author_id": "3126044440",
  "text": "@webprolific Knip helped me get rid of over 41k lines of code in legacy codebase 🥺🥺💕",

  "id": "1729181106715632088",
  "created_at": "2023-11-27T16:51:17.000Z",
  "edit_history_tweet_ids": ["1729181106715632088"]
},
{
  "entities": {
    "mentions": [
      {
        "username": "contra",
        "id": "973313523790082053"
      }
    ],
    "annotations": [
      {
        "probability": 0.8474,
        "type": "Other",
        "normalized_text": "Knip"
      }
    ],
    "urls": [
      {
        "url": "https://t.co/UgCunYQWzu",
        "expanded_url": "https://twitter.com/webprolific/status/1729105865683435542",
        "display_url": "twitter.com/webprolific/st…"
      }
    ]
  },
  "author_id": "95668959",
  "text": "Big fans of Knip at @Contra. Such an extremely well developed and maintained project https://t.co/UgCunYQWzu",

  "id": "1729157761215369264",
  "created_at": "2023-11-27T15:18:31.000Z",
  "edit_history_tweet_ids": ["1729157761215369264"]
},
{
  "entities": {
    "mentions": [
      {
        "username": "webprolific",
        "id": "218833730"
      }
    ],
    "urls": [
      {
        "url": "https://t.co/ZUYYdvkBJc",
        "expanded_url": "https://github.com/getsentry/sentry/",
        "display_url": "github.com/getsentry/sent…",
        "title": "GitHub - getsentry/sentry: Developer-first error tracking and performance monitoring",
        "description": "Developer-first error tracking and performance monitoring - GitHub - getsentry/sentry: Developer-first error tracking and performance monitoring",
        "unwound_url": "https://github.com/getsentry/sentry/"
      }
    ]
  },
  "author_id": "1632752318994153472",
  "text": "@webprolific Just tried this on https://t.co/ZUYYdvkBJc - got some super useful results! Nice work.",

  "id": "1727040036334424406",
  "created_at": "2023-11-21T19:03:26.000Z",
  "edit_history_tweet_ids": ["1727040036334424406"]
},
{
  "entities": {
    "mentions": [
      { "username": "TkDodo", "id": "44217212" },
      {
        "username": "webprolific",
        "id": "218833730"
      },
      {
        "username": "webprolific",
        "id": "218833730"
      }
    ]
  },
  "author_id": "1513218780167655427",
  "text": "@TkDodo @webprolific I've manage to delete 6k LOC in the last 30 minutes 🫣\n\nGreat job here @webprolific 👍🏽",

  "id": "1726807293583609935",
  "created_at": "2023-11-21T03:38:35.000Z",
  "edit_history_tweet_ids": ["1726807293583609935"]
},
{
  "entities": {
    "mentions": [
      {
        "username": "webprolific",
        "id": "218833730"
      }
    ],
    "urls": [
      {
        "url": "https://t.co/Tg5E6C7gqa",
        "expanded_url": "https://twitter.com/housecor/status/1713975785273303286",
        "display_url": "twitter.com/housecor/statu…"
      }
    ]
  },
  "author_id": "44217212",
  "text": "knip is an amazing tool. Shoutout to @webprolific for building it 🙌.\n\nNot cleaning up correctly has a real maintenance cost. I've deleted lots of dead code - functions that were only used in tests and components that were only used in stories - all thanks to knip 🚀. https://t.co/Tg5E6C7gqa",

  "id": "1714023231689031941",
  "created_at": "2023-10-16T20:59:18.000Z",
  "edit_history_tweet_ids": ["1714023231689031941"]
},
{
  "entities": {
    "annotations": [
      {
        "probability": 0.9562,
        "type": "Other",
        "normalized_text": "JavaScript"
      },
      {
        "probability": 0.8666,
        "type": "Other",
        "normalized_text": "TypeScript"
      }
    ],
    "urls": [
      {
        "url": "https://t.co/f3fzC5UPtR",
        "expanded_url": "https://github.com/webpro-nl/knip",
        "display_url": "github.com/webpro-nl/knip",
        "images": [
          {
            "url": "https://pbs.twimg.com/news_img/1729485824314548224/0xZughNs?format=png&name=orig",
            "width": 1280,
            "height": 640
          },
          {
            "url": "https://pbs.twimg.com/news_img/1729485824314548224/0xZughNs?format=png&name=150x150",
            "width": 150,
            "height": 150
          }
        ],
        "title": "GitHub - webpro-nl/knip: ✂️ Find unused dependencies, exports and files in your JavaScript and TypeScript projects. Knip it before you ship it!",
        "description": "✂️ Find unused dependencies, exports and files in your JavaScript and TypeScript projects. Knip it before you ship it! - GitHub - webpro-nl/knip: ✂️ Find unused dependencies, exports and files in yo...",
        "unwound_url": "https://github.com/webpro-nl/knip"
      }
    ]
  },
  "author_id": "481186762",
  "text": "🛠️ Knip\n\n👉🏻 Knip finds unused dependencies, exports and files in your JavaScript and TypeScript projects.\n👉🏻 Less code and dependencies lead to improved performance, less maintenance and easier refactorings. \n\nhttps://t.co/f3fzC5UPtR",
  "id": "1696221274039595363",
  "created_at": "2023-08-28T18:00:40.000Z",
  "edit_history_tweet_ids": ["1696221274039595363"]
},
{
  "entities": {
    "annotations": [
      {
        "probability": 0.9354,
        "type": "Other",
        "normalized_text": "JavaScript"
      },
      {
        "probability": 0.7027,
        "type": "Other",
        "normalized_text": "TypeScript"
      },
      {
        "probability": 0.7208,
        "type": "Other",
        "normalized_text": "Josh"
      }
    ],
    "urls": [
      {
        "url": "https://t.co/nKtvr2wEg6",
        "expanded_url": "https://twitter.com/JoshuaKGoldberg/status/1693713311836115286",
        "display_url": "twitter.com/JoshuaKGoldberg"
      }
    ]
  },
  "author_id": "416394303",
  "text": "Knip is wonderful at finding out unused code/dependencies in a legacy JavaScript/TypeScript application\n\nRecommend 💯\n\nHere's how Josh used it to remove code bloat in Centered: https://t.co/nKtvr2wEg6",
  "id": "1693944495472046382",
  "created_at": "2023-08-22T11:13:34.000Z",
  "edit_history_tweet_ids": ["1693944495472046382"]
},
{
  "entities": {
    "annotations": [
      {
        "probability": 0.5513,
        "type": "Other",
        "normalized_text": "Knip"
      }
    ],
    "urls": [
      {
        "url": "https://t.co/L8w9V11PRL",
        "expanded_url": "https://www.smashingmagazine.com/2023/08/knip-automated-tool-find-unused-files-exports-dependencies/",
        "display_url": "smashingmagazine.com/2023/08/knip-a…",
        "images": [
          {
            "url": "https://pbs.twimg.com/news_img/1727384011872366592/W-M5klkJ?format=jpg&name=orig",
            "width": 1200,
            "height": 675
          },
          {
            "url": "https://pbs.twimg.com/news_img/1727384011872366592/W-M5klkJ?format=jpg&name=150x150",
            "width": 150,
            "height": 150
          }
        ],
        "title": "Knip: An Automated Tool For Finding Unused Files, Exports, And Dependencies — Smashing Magazine",
        "description": "Most of the projects have at least a few unused files, exports, and dependencies lying around, often because it’s difficult knowing when one thing relies on another and scary removing something you’re not sure is in use. Lars Kappert shares a tool he’s been working on that offers a solution.",
        "unwound_url": "https://www.smashingmagazine.com/2023/08/knip-automated-tool-find-unused-files-exports-dependencies/"
      }
    ]
  },
  "author_id": "15736190",
  "text": "Unused files, exports, and dependencies often clutter projects, as it’s difficult knowing when one thing relies on another and scary to remove something you’re not sure is in use. 👀\n\n↬ Meet Knip, which scours projects for these unused artifacts: https://t.co/L8w9V11PRL",
  "id": "1691120132901240832",
  "created_at": "2023-08-14T16:10:33.000Z",
  "edit_history_tweet_ids": ["1691120132901240832"]
},
{
  "entities": {
    "mentions": [
      {
        "username": "typescript",
        "id": "809233214"
      }
    ],
    "urls": [
      {
        "url": "https://t.co/1mRMwvjIpw",
        "expanded_url": "https://effectivetypescript.com/2023/07/29/knip/",
        "display_url": "effectivetypescript.com/2023/07/29/kni…",
        "images": [
          {
            "url": "https://pbs.twimg.com/news_img/1696204795353219085/ZjmFlTVW?format=jpg&name=orig",
            "width": 560,
            "height": 735
          },
          {
            "url": "https://pbs.twimg.com/news_img/1696204795353219085/ZjmFlTVW?format=jpg&name=150x150",
            "width": 150,
            "height": 150
          }
        ],
        "title": "Effective TypeScript › Recommendation Update: ✂️ Use knip to detect dead code and types",
        "description": "TL;DR: Use ts-prune is in maintenance mode. Use knip to find dead code instead. It's great!",
        "unwound_url": "https://effectivetypescript.com/2023/07/29/knip/"
      }
    ]
  },
  "author_id": "21046936",
  "text": "Recommendation update: use knip to find dead code, types and dependencies in your @typescript ✂️https://t.co/1mRMwvjIpw",
  "id": "1685298103094554625",
  "created_at": "2023-07-29T14:35:53.000Z",
  "edit_history_tweet_ids": ["1685298103094554625"]
},
{
  "entities": {
    "annotations": [
      {
        "probability": 0.9661,
        "type": "Other",
        "normalized_text": "GitHub"
      }
    ],
    "urls": [
      {
        "url": "https://t.co/vnxNbiJs1I",
        "expanded_url": "https://github.com/webpro-nl/knip",
        "display_url": "github.com/webpro-nl/knip",
        "images": [
          {
            "url": "https://pbs.twimg.com/news_img/1729485824314548224/0xZughNs?format=png&name=orig",
            "width": 1280,
            "height": 640
          },
          {
            "url": "https://pbs.twimg.com/news_img/1729485824314548224/0xZughNs?format=png&name=150x150",
            "width": 150,
            "height": 150
          }
        ],
        "title": "GitHub - webpro-nl/knip: ✂️ Find unused dependencies, exports and files in your JavaScript and TypeScript projects. Knip it before you ship it!",
        "description": "✂️ Find unused dependencies, exports and files in your JavaScript and TypeScript projects. Knip it before you ship it! - GitHub - webpro-nl/knip: ✂️ Find unused dependencies, exports and files in yo...",
        "unwound_url": "https://github.com/webpro-nl/knip"
      }
    ]
  },
  "author_id": "380616577",
  "text": "This is worth checking out on GitHub. 👍 https://t.co/vnxNbiJs1I",
  "id": "1581910299846012928",
  "created_at": "2022-10-17T07:29:40.000Z",
  "edit_history_tweet_ids": ["1581910299846012928"]
},
{
  "note_tweet": {
    "text": "Streamlining React projects with Knip:\n\nRecently, I employed Knip for a project, and it worked wonders! \n\nIt efficiently resolved issues with removing\n✅ Unused files, \n✅ Unused npm dependencies, \n✅ Unneeded TypeScript exports.\n\n A real time-saver for maintaining a clean and efficient codebase. 🛠️ #ReactDevelopment \n\nhttps://t.co/IbJH2fIzsE",
    "entities": {
      "urls": [
        {
          "url": "https://t.co/IbJH2fIzsE",
          "expanded_url": "https://github.com/webpro-nl/knip",
          "display_url": "github.com/webpro-nl/knip"
        }
      ],
      "hashtags": [{ "tag": "ReactDevelopment" }]
    }
  },
  "entities": {
    "annotations": [
      {
        "probability": 0.743,
        "type": "Other",
        "normalized_text": "Knip"
      },
      {
        "probability": 0.7667,
        "type": "Other",
        "normalized_text": "Knip"
      }
    ],
    "urls": [
      {
        "url": "https://t.co/P39xg0qCrT",
        "expanded_url": "https://twitter.com/i/web/status/1693502146128281657",
        "display_url": "twitter.com/i/web/status/1…"
      }
    ]
  },
  "author_id": "827145937030148097",
  "text": "Streamlining React projects with Knip:\n\nRecently, I employed Knip for a project, and it worked wonders! \n\nIt efficiently resolved issues with removing\n✅ Unused files, \n✅ Unused npm dependencies, \n✅ Unneeded TypeScript exports.\n\n A real time-saver for maintaining a clean and… https://t.co/P39xg0qCrT",
  "id": "1693502146128281657",
  "created_at": "2023-08-21T05:55:50.000Z",
  "edit_history_tweet_ids": ["1693502146128281657"]
},
{
  "entities": {
    "annotations": [
      {
        "probability": 0.9331,
        "type": "Other",
        "normalized_text": "JavaScript"
      },
      {
        "probability": 0.5907,
        "type": "Other",
        "normalized_text": "Knip"
      }
    ]
  },
  "author_id": "18191376",
  "text": "Found a bunch of unused code, -dependencies, and unnecessary exports. Had just one false positive but overall pretty good. 10/10 would recommend.\n\nIf you’ve got a JavaScript package/project use Knip and remove unnecessary code. ✂️",
  "id": "1692942539614028086",
  "created_at": "2023-08-19T16:52:09.000Z",
  "edit_history_tweet_ids": ["1692942539614028086"]
}

],
"includes": {
"media": [
{
"media_key": "1",
"url": "https://pbs.twimg.com/media/GDrwdB3aYAAu4am?format=png&name=orig",
"type": "photo"
},
{
"media_key": "2",
"url": "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:opw3iapscc55nlgrx6q2yjah/bafkreihzszb56agbe6dtuf6xfutl65mjg5ethnrvw264d5bljbm377jora@jpeg",
"type": "photo"
},
{
"media_key": "3_1730179997682552832",
"url": "https://pbs.twimg.com/media/GALVin9a0AAsn0-.jpg",
"type": "photo"
},
{
"media_key": "4",
"url": "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:4dsclu5hjmht3shxutxtge6h/bafkreid63o3hwvwpk6emgzj64vywusztofhz2yihuvwkywuuajn4eekvom@jpeg",
"type": "photo"
},
{
"media_key": "5",
"url": "https://cdn.bsky.app/img/feed_fullsize/plain/did:plc:2gkh62xvzokhlf6li4ol3b3d/bafkreiahk2jbsb2stw276lkrkxrkvialqs25zrpgo2n3cigzfb7qvgfsmq@jpeg",
"type": "photo"
},
{
"media_key": "6",
"url": "https://pbs.twimg.com/media/G9wBaMFWoAAkgv5.png",
"type": "photo"
}
],
"users": [
{
"id": "22",
"username": "molliboyle.bsky.social",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:uwwm2qtpni4gvzzhujhh56ps/bafkreib5w76mmc6lxazhkymadd3oj5skped63b6ugvswiikrss7ljgcfi4@jpeg",
"name": "Molly Boyle"
},
{
"id": "20",
"username": "RozenMD",
"profile_image_url": "https://unavatar.io/x/RozenMD",
"name": "Max Rozen"
},
{
"id": "21",
"username": "06_Shreehari",
"profile_image_url": "https://unavatar.io/x/06_Shreehari",
"name": "Shreehari"
},
{
"id": "19",
"username": "janis.me",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:kmekwulhn26kgfwjzrry76d7/bafkreihtzvhhk7oz6cjbgnsbaravu46zhai3zwogkcg7hxv3dlhvecrqw4@jpeg",
"name": "Janis Jansen"
},
{
"id": "11",
"username": "julien.deniau.me",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:4dsclu5hjmht3shxutxtge6h/bafkreiht3iqxcmelwe7oysawrdpynzevf4dt2ndzeln4f5c2izf62zm57q@jpeg",
"name": "Julien Deniau"
},
{
"id": "12",
"username": "alexharfordseo.bsky.social",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:y3arowkhgiexpyvmmxvquavg/bafkreiazlcx5fdxylsqtsdng3beq3ayuoc7nzyqcddeequhi7uploqcjny@jpeg",
"name": "AlexHarford-TechSEO"
},
{
"id": "13",
"username": "fubits.dev",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:6aglx53tojyuwxwueap5og3h/bafkreiclnob3mnfvo74an6ugrnbdikrkxcskndjojrdtkwf5vwjuu77z5u@jpeg",
"name": "Ilja"
},
{
"id": "14",
"username": "alexanderkaran.bsky.social",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:3n4fccuhdomkkebojjdurnco/bafkreiggltcvdoh4igf5yxvrxggysmasv5efn6hffyav2o6swxslbc3lp4@jpeg",
"name": "Alexander Karan"
},
{
"id": "15",
"username": "patak.dev",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:2gkh62xvzokhlf6li4ol3b3d/bafkreibi7rw745tbcea7ljrpbkkkoragy2xa5a2lrxhdb3m23s7n3wkv3q@jpeg",
"name": "patak"
},
{
"id": "16",
"username": "psparwez",
"profile_image_url": "https://unavatar.io/x/psparwez",
"name": "PS"
},
{
"id": "17",
"username": "k_shehadeh",
"profile_image_url": "https://unavatar.io/x/k_shehadeh",
"name": "Karim Shehadeh"
},
{
"id": "18",
"username": "cramforce",
"profile_image_url": "https://unavatar.io/x/cramforce",
"name": "Malte Ubl"
},
{
"id": "1",
"username": "sqs",
"profile_image_url": "https://unavatar.io/x/sqs",
"name": "Quinn Slack"
},
{
"id": "2",
"username": "jokull",
"profile_image_url": "https://unavatar.io/x/jokull",
"name": "Jökull Solberg"
},
{
"id": "3",
"username": "Hicksyfern",
"profile_image_url": "https://unavatar.io/x/Hicksyfern",
"name": "Tom Hicks"
},
{
"id": "4",
"username": "daviduzumeri.bsky.social",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:6ykajndqngy2kxozo37mpsgc/bafkreif2xu6bini3tpv2hfauw53ng43zbefgqol36yt4baehcbdfxahihu@jpeg",
"name": "Old Man Uzi"
},
{
"id": "5",
"username": "gary__tyr",
"profile_image_url": "https://unavatar.io/x/gary__tyr",
"name": "Gary Tyr"
},
{
"id": "6",
"username": "KevinVanCott",
"profile_image_url": "https://unavatar.io/x/KevinVanCott",
"name": "Kevin Thomas Van Cott"
},
{
"id": "7",
"username": "joshuakgoldberg.com",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:hwtki3j7oghodc7h6gqnrtro/bafkreicvkrpjmc7yoviwf5vhkecgcgyn23x24ix4aqdwiikcl226c64k2q@jpeg",
"name": "Josh Goldberg 💖"
},
{
"id": "8",
"username": "beaussan.io",
"profile_image_url": "https://cdn.bsky.app/img/avatar/plain/did:plc:opw3iapscc55nlgrx6q2yjah/bafkreiaj72657msogojjdk4wr76fe3umimydon3pzzrquuj7opcftntrqa@jpeg",
"name": "Nicolas Beaussart"
},
{
"id": "9",
"username": "MidnightDesign",
"name": "Rudolph Gottesheim",
"profile_image_url": "https://avatars.githubusercontent.com/u/743172?v=4"
},
{
"id": "10",
"username": "stephaneledorze",
"name": "stephane le dorze",
"profile_image_url": "https://unavatar.io/x/stephaneledorze"
},
{
"id": "22918124",
"username": "gregberge_",
"profile_image_url": "https://unavatar.io/x/gregberge_",
"name": "Greg Bergé"
},
{
"id": "19268321",
"username": "housecor",
"profile_image_url": "https://unavatar.io/x/housecor",
"name": "Cory House"
},
{
"id": "3126044440",
"username": "pkgacek",
"profile_image_url": "https://unavatar.io/x/pkgacek",
"name": "Piotr Gacek 🐟"
},
{
"id": "95668959",
"username": "kuizinas",
"profile_image_url": "https://unavatar.io/x/kuizinas",
"name": "Gajus"
},
{
"id": "1632752318994153472",
"username": "imabhiprasad",
"profile_image_url": "https://unavatar.io/x/imabhiprasad",
"name": "Abhijeet Prasad"
},
{
"id": "1513218780167655427",
"username": "fernandoeeu_dev",
"profile_image_url": "https://unavatar.io/x/fernandoeeu_dev",
"name": "Fernando"
},
{
"id": "44217212",
"username": "TkDodo",
"profile_image_url": "https://unavatar.io/x/TkDodo",
"name": "Dominik 🔮"
},
{
"id": "481186762",
"username": "Mokkapps",
"profile_image_url": "https://unavatar.io/x/Mokkapps",
"name": "Michael Hoffmann"
},
{
"id": "416394303",
"username": "nicoespeon",
"profile_image_url": "https://unavatar.io/x/nicoespeon",
"name": "Nicolas Carlo"
},
{
"id": "15736190",
"username": "smashingmag",
"profile_image_url": "https://unavatar.io/x/smashingmag",
"name": "Smashing Magazine 🇺🇦 🏳️‍🌈"
},
{
"id": "21046936",
"username": "danvdk",
"profile_image_url": "https://unavatar.io/x/danvdk",
"name": "Dan Vanderkam"
},
{
"id": "380616577",
"username": "stefanjudis",
"profile_image_url": "https://unavatar.io/x/stefanjudis",
"name": "Stefan Judis"
},
{
"id": "827145937030148097",
"username": "BKailaash",
"profile_image_url": "https://unavatar.io/x/BKailaash",
"name": "Kailaash"
},
{
"id": "18191376",
"username": "waldekm",
"profile_image_url": "https://unavatar.io/x/waldekm",
"name": "Waldek Mastykarz"
}
]
}
}


Packages Docs Src Content Docs Playground (`packages/docs/src/content/docs/playground.mdx`)


title: 'Playground' template: splash

Welcome to the Knip playground. The codebases below have linting issues on
purpose in the codebases, so Knip will report various types of issues.

Feel free to play around! There are currently two playgrounds:

Shoutout to StackBlitz for generously
providing these free dev containers.

Basic

Visit this Knip playground on StackBlitz to try out Knip:

Monorepo

Visit this Knip playground on StackBlitz to try out Knip in a monorepo
setting:


Packages Docs Src Content Docs Sponsors (`packages/docs/src/content/docs/sponsors.mdx`)


title: 'Become a sponsor!' template: splash

import StarlightPage from '@astrojs/starlight/components/StarlightPage.astro';
import Posts from '../../components/Posts.astro';
import Projects from '../../components/Projects.astro';
import SponsorsComponent from '../../components/Sponsors.astro';
import SponsorsChart from '../../components/SponsorsChart.astro';

:::article{.prose}

Knip is a powerful tool that helps thousands of developers keep their JavaScript
and TypeScript projects lean and maintainable. Knip saves teams valuable time
and resources.

Building and maintaining Knip has been, and remains, a lot of work. Supportive
resources allow me to focus on maintenance and development of Knip, which is
relied upon by many in the JavaScript community. Please consider
supporting the project! The following platforms are available to support Knip:

You or your company logo with a backlink to your website will be added to this
page if you decide to support the project on an ongoing basis. Eternal gratitude
to the companies and people supporting the project!

:::

Monthly Overview

Overview of gross GitHub and OpenCollective sponsorships and invoices, starting
from the moment the project openly asked for support. Actual development started
one year before that.

  • The monthly aggregated average over the charted period is $880.
  • The monthly recurring average without one-time payments is $492.
  • One-time donations of at least $1000:
  • GitHub Sponsors is for my GitHub account. This has more repositories, but in
    practice targets mostly Knip (and perhaps some release-it).
  • Chart generated using Venz.

Trusted by the world's best software teams

Don't just take our word for it


Packages Docs Src Content Docs Blog Brief History (`packages/docs/src/content/docs/blog/brief-history.mdx`)


title: A Brief History Of Knip
date: 2023-10-15
authors:

  • lars
    tags:
  • History

If you are fond of short lists and brief histories, then this page was written
just for you!

{/* excerpt */}

  • 2022-10-04: The initial commit. Still so tiny at that point, but the seed
    was planted. Starting out with finding unused files and exports, the name was
    Exportman! 🦸
  • 2022-10-09: Big plans and a rename 5 days later, the first published version
    of Knip was v0.1.2.
  • 2022-11-22: Unused dependencies and support for workspaces and plugins in the
    first alpha of v1.
  • 2023-01-10: Lots of testing and fixes led to Knip v1.
  • 2023-03-22: Knip v2 saw a full rewrite of the TypeScript backend.
  • 2023-10-15: Introduction of Knip v3.

Packages Docs Src Content Docs Blog For Editors And Agents (`packages/docs/src/content/docs/blog/for-editors-and-agents.mdx`)


title: Knip for Editors & Agents
date: 2025-12-17
authors:

  • lars
    tags:
  • Announcement

Three years in, Knip has found its place in over 10.000 projects and is
downloaded over 18M times/month. A long period of steady growth in usage
and stability allows Knip to become more accessible to more people. That's why
I'm excited and proud to introduce the brand new Editor Extension and
MCP Server. For humans and coding agents alike, Knip will help keep your
codebases tidy.

Don't forget... Knip it before you ship it!

{/* excerpt */}

Editor Extension

This one is for you.

The usual suspects like red squiggles for unused exports are there. What
really moves the needle for DX with Knip's module graph is navigation. A
completely unique way to view & fly through codebases. Connect the dots during
development and refactors, while keeping things in check. We're starting out
with 3 key features:

  1. Hover over Export for import & usage locations
  2. Imports Tree View for direct links to implementations
  3. Exports Tree View for direct links to import & usage locations

The extension has a built-in MCP Server with a command and resources to
configure Knip for you, completely automated.

Find Knip on the VS Code Marketplace and find Knip in the Open VSX
Registry
.

MCP Server

Configuring Knip has always been a major headache to many. No more. Tell your
coding agent to "configure knip" and it will RTFM so you don't have to. Using a
newer model like Opus 4.5 or GPT 5.2 results in an optimized knip.json file
and an uncluttered codebase.

The MCP Server is available separately and built into the VS Code
Extension.

Language Server

The VS Code Extension and the MCP Server are powered by the new Language Server.
It's a custom server that builds the full module graph of your project, and
provides a session with a graph explorer to request all sorts of interesting
information. Queries like "where is an export imported" or "is this import part
of a circular dependency" are just scratching the surface here.

Extensions for other IDEs can be built on top. See
language-server/README.md

Screenshots

Lint Findings

Lint Findings

Imports & Exports

hover

Contention

The IDE extension shows extra issues in the tree views like circular
dependencies. We're starting out with some extra novelties like conflicting and
branched/diamond-shaped import chains.

Circular Dependencies

If an import is part of a circular dependency, Knip will display:

Circular Dependencies

Conflicts

TypeScript shows direct conflicts when importing or re-exporting the same named
export from different files. Except when the problem is more subtle and the
chain spans more than one file. Knip warns:

Conflicts

Branching

Branched or diamond-shaped imports chains indicate unnecessary re-exports and
complexity. They help to untangle large codebases and shrink or get rid of
barrel files. Knip warns:

Branching

VS Code Extension Settings

VS Code Extension Settings


Packages Docs Src Content Docs Blog Knip V1 (`packages/docs/src/content/docs/blog/knip-v1.mdx`)


title: Announcing Knip v1
date: 2023-01-04
authors:

  • lars
    tags:
  • Announcement
    excerpt: Learn how to migrate to Knip v1.

When coming from version v0.13.3 or before, there are some breaking changes:

  • The entryFiles and projectFiles options have been renamed to entry and
    project.
  • The --dev argument and dev: true option are gone, this is now the default
    mode (see production mode).
  • Workspaces have been moved from the root of the config to the workspaces key
    (see workspaces).
  • The --dir argument has been renamed to --workspace.

Example

A configuration like this in v0.13.3 or before...

json
{
  "entryFiles": ["src/index.ts"],
  "projectFiles": ["src/**/*.ts", "!**/*.spec.ts"],
  "dev": {
    "entryFiles": ["src/index.ts", "src/**/*.spec.ts", "src/**/*.e2e.ts"],
    "projectFiles": ["src/**/*.ts"]
  }
}

...should become this for v1...

json
{
  "entry": ["src/index.ts!"],
  "project": ["src/**/*.ts!"]
}

Much cleaner, right? For some more details:

  • The dev property for the --dev flag is now the default mode.
  • Use --production to analyze only the entry and project files suffixed
    with !.
  • The glob patterns for both types of test files (*.spec.ts and *.e2e.ts)
    are no longer needed:
    • Regular test files like *.test.js and *.spec.ts etc. are automatically
      handled by Knip.
    • The *.e2e.ts files is configured with the Cypress or other plugin. Note
      that Cypress uses *.cy.ts for spec files, but this could be overridden
      like so:
json
{
  "entry": "src/index.ts!",
  "project": "src/**/*.ts!",
  "cypress": {
    "entry": "src/**/*.e2e.ts"
  }
}

Packages Docs Src Content Docs Blog Knip V2 (`packages/docs/src/content/docs/blog/knip-v2.mdx`)


title: Announcing Knip v2
date: 2023-03-22
authors:

  • lars
    tags:
  • Announcement
    excerpt: Learn what's new in Knip v2.

Breaking changes

When coming from v1, there are no breaking changes in terms of configuration.

Changes

There are some changes regarding CLI arguments and output:

  • Knip now runs on every workspace automatically (except for the ones in
    ignoreWorkspaces: []).
  • The "Unlisted or unresolved dependencies" is split in "Unlisted dependencies"
    and "Unresolved imports".
  • Bug fixes and increased correctness impact output (potentially causing CI to
    now succeed or fail).

New features

Rewriting a major part of Knip's core from scratch allows for some new exciting
features:

  • Performance. Files are read only once, and their ASTs are traversed only
    once. Projects of any size will notice the difference. Total running time for
    some projects decreases with 90%.
  • Compilers. You can now include other file types such as .mdx, .vue and
    .svelte in the analysis.

Internally, the ts-morph dependency is replaced by typescript itself.

Other improvements

  • Improved support for workspaces.
  • Improved module resolutions, self-referencing imports, and other things you
    don't want to worry about.
  • Configure ignoreDependencies and ignoreBinaries at the workspace level.
  • Simplified plugins model: plugin dependency finder may now return any type of
    dependency in a single array: npm packages, local workspace packages, local
    files, etc. (module and path resolution are handled outside the plugin).
  • Many bugfixes.

Packages Docs Src Content Docs Blog Knip V3 (`packages/docs/src/content/docs/blog/knip-v3.mdx`)


title: Announcing Knip v3
date: 2023-10-15
authors:

  • lars
    tags:
  • Announcement
    excerpt: Learn what's new in Knip v3.

import { Tabs, TabItem } from '@astrojs/starlight/components';

Lots of new users got introduced to Knip, coming with clear bug reports, helpful
insights, superb reproductions and great suggestions this year. You're all a
friendly and helpful bunch! Recently I've opened a Discord channel where more
communication, collaboration, ideas and updates are happening: feel free to join
The Knip Barn!

Today, Knip has over 140k weekly downloads on npm, almost 4000 stars on
GitHub
, and over 500 repositories using it. While numbers are just
numbers, they do add to the positive feedback I'm receiving daily. Everything
combined makes me think I'm on the right track which is very motivating to keep
working on Knip.

So... What's Been Cooking Lately?

  • Migration to a monorepo setup
  • This very website built with Starlight 🌟
  • Extended documentation for just about everything
  • Improved JSON reporter for external integrations (e.g. GitHub Action)
  • Some breaking changes, but you probably don't need to make any changes

Breaking Changes

A major bump comes with breaking changes, but most likely no changes necessary
on your end:

  • Removed support for Node.js v16, Knip v3 requires at least Node.js v18.6
  • Simplified exit codes
  • Production mode now includes types by default (add --exclude types for
    previous behavior)
  • Removed --ignore-internal flag; @internal exports ignored in
    production mode now
  • The --debug-file-filter flag is removed
  • The jsonExt reporter is now the default JSON reporter (the previous one
    is gone)
  • Moved typescript to peerDependencies (requires >=5.0.4)

Installation

Try out the latest Knip v3 release today!

```shell npm install -D knip ``` ```shell pnpm add -D knip ``` ```shell bun add -D knip ``` ```shell yarn add -D knip ```

Remember, Knip it before you ship it! Have a great day ☀️


Packages Docs Src Content Docs Blog Knip V4 (`packages/docs/src/content/docs/blog/knip-v4.mdx`)


title: Announcing Knip v4
date: 2024-01-16
authors:

  • lars
    tags:
  • Announcement
    excerpt: Learn what's new in Knip v4.

import { Tabs, TabItem } from '@astrojs/starlight/components';

I'm happy to announce that Knip v4 is available!

The work took over a month and the process of slimming down to speed up
ended up really well: significant faster runs and reduced memory usage. In the
meantime, v3 continued to receive more contributions, plugins and bug fixes.

Highlights

Compared to v3, here are the highlights:

  • Performance: significant speed bump (up to 80%!)
  • Performance: globbing in combo with .gitignore is a lot more efficient
  • Configuration: built-in compilers (for Astro, MDX, Svelte & Vue)
  • The ignore option has been improved
  • Internal refactoring to serialize data for future improvements like caching.

The actual performance win in your projects depends on various factors like size
and complexity.

Major Changes

The changes have been tested against various repositories, but it's possible
that you will encounter false positives caused by the major refactoring that has
been done. If you do, please report!

Unused Class Members

Finding unused class members is no longer enabled by default. Here's why it's
now opt-in:

  • When using Knip for the first time on a large repository it can crash after a
    while with an out of memory error. This is a terrible experience.
  • Plenty of codebases don't use classes at all, keeping TS programs in memory is
    a waste of resources.
  • Many configurations already exclude classMembers from the output.

Enable unused class members by using the CLI argument or the configuration
option:

shell
knip --include classMembers
json
{
  "include": ["classMembers"]
}

Now that unused class members is opt-in and better organized within Knip, it
might be interesting to start looking at opt-ins for other unused members, such
as those of types and interfaces.

By the way, enum members are "cheap" with the v4 refactor, so those are still
included by default.

Compilers

You can remove the compilers option from your configuration. Since you can
override them, your custom compilers can stay where they are. This also means
that you can go back from knip.ts to knip.json if you prefer.

Ignore Files

The ignore option accepted patterns like examples/, but if you want to
ignore the files inside this folder you should update to globs like
examples/**.

What's Next?

The refactoring for this release opens the door to more optimizations, such as
caching. I'm also very excited to see how deeper integrations such as in GitHub
Actions or IDEs like VS Code or WebStorm may further develop.

Remember, if you are you using Knip at work your company can sponsor me!

One More Thing...

An idea I've been toying with is "tagged exports". The idea is that you can tag
exports in a JSDoc comment. The tag does not need to be part of the JSDoc or
TSDoc spec. For example:

ts
/** @custom */
export const myExport = 1;

Then, include or exclude such tagged exports from the report like so:

shell
knip --experimental-tags=+custom
knip --experimental-tags=-custom,-internal

This way, you can either focus on or ignore specific tagged exports with tags
you define yourself. This also works for individual class or enum members.

Once this feature is intuitive and stable, the experimental flag will be
removed and option(s) added to the Knip configuration file. The docs are in the
CLI reference.

Let's Go!

What are you waiting for? Start using Knip v4 today!

```shell npm install -D knip ``` ```shell pnpm add -D knip ``` ```shell bun add -D knip ``` ```shell yarn add -D knip ```

Remember, Knip it before you ship it! Have a great day ☀️


Packages Docs Src Content Docs Blog Knip V5 (`packages/docs/src/content/docs/blog/knip-v5.mdx`)


title: Announcing Knip v5
date: 2024-02-10
authors:

  • lars
    tags:
  • Announcement
    excerpt: Learn what's new in Knip v5.

import { Tabs, TabItem } from '@astrojs/starlight/components';

Today brings the smallest major release so far. Tiny yet mighty!

Below are two cases to demonstrate the change in how unused exports are
reported.

Case 1

The first case shows two exports with a namespaced import that references one of
those exports explicitly:

ts title="knip.js"
export const version = 'v5';
export const getRocket = () => '🚀';
ts title="index.js"
import * as NS from './knip.js';

console.log(NS.version);

In this case we see that getRocket is an unused export.

Previously it would go into the "Unused exports in namespaces" category
(nsExports). This issue has been moved to the "Unused exports" category
(exports).

Case 2

The second case is similar, but only the imported namespace itself is
referenced. None of the individual exports is referenced:

ts title="index.js"
import * as NS from './knip.js';
import send from 'stats';

send(NS);

Are the version and getRocket exports used? We can't know. The same is true
for the spread object pattern:

ts title="index.js"
import * as NS from './knip.js';

const Spread = { ...NS };

Previously those exports would go into the "Unused exports in namespaces"
category. This is still the case, but this category is no longer enabled by
default.

Include unused exports in namespaces

To enable this type of issues in Knip v5, add this argument to the command:

shell
knip --include nsExports

Or in your configuration file:

json title="knip.json"
{
  "include": ["nsExports", "nsTypes"]
}

Now version and getRocket will be reported as "Unused exports in
namespaces".

Note that nsExports and nsTypes are split for more granular control.

Handling exports in namespaced imports

You have a few options to handle namespaced imports when it comes to unused
exports.

1. Use named imports

Regardless of whether nsExports is enabled or not, it's often good practice to
replace the namespaced imports with named imports:

ts title="index.js"
import { version, getRocket } from './knip.js';

send({ version, getRocket });

Whenever possible, explicit over implicit is often the better choice.

2. Standardized JSDoc tags

Using one of the available JSDoc tags like @public or @internal:

ts title="knip.js"
export const version = 'v5';
/** @public */
export const getRocket = () => '🚀';

Assuming only imported using a namespace (like in the example cases above), this
will exclude the getRocket export from the report, even though it isn't
explicitly referenced.

3. Arbitrary JSDoc tags

Another solution is to tag individual exports arbitrarily:

ts title="knip.js"
export const version = 'v5';
/** @launch */
export const getRocket = () => '🚀';

And then exclude the tag like so:

shell
$ knip --experimental-tags=-launch
Exports in used namespace (1)
version  NS  unknown  knip.js:1:1

Assuming only imported using a namespace (like in the example cases above), this
will exclude the getRocket export from the report, even though it isn't
explicitly referenced.

A better default

I believe this behavior in v5 is the better default: have all exports you want
to know about in a single category, and those you probably want to ignore in
another that's disabled by default.

Before the v4 refactoring, this would be a lot harder to implement. That
refactoring turns out to be a better investment than expected. Combined with a
better understanding of how people write code and use Knip, this change is a
natural iteration.

Why the major bump? It's not breaking for the large majority of users, but for
some it may be breaking. For instance when relying on the JSON reporter,
other reporter output, or custom preprocessing. It's not a bug fix, it's
not a new feature, but since semver is all about setting expectations I feel the
change is large enough to warrant a major bump.

Let's Go!

What are you waiting for? Start using Knip v5 today!

```shell npm install -D knip ``` ```shell pnpm add -D knip ``` ```shell bun add -D knip ``` ```shell yarn add -D knip ```

Remember, Knip it before you ship it! Have a great day ☀️


Packages Docs Src Content Docs Blog Knip V6 (`packages/docs/src/content/docs/blog/knip-v6.mdx`)


title: Announcing Knip v6
date: 2026-03-20
authors:

  • lars
    tags:
  • Announcement
    excerpt: Learn what's new in Knip v6.

Knip v6 is out!

This release is all about replacing the TypeScript backend entirely with
oxc-parser and oxc-resolver, and making Knip a whole lot faster!

From TypeScript to oxc

Two years ago, the "slim down to speed up" and Knip v4 work removed a
lot of overhead around TypeScript programs, made serialization and caching
practical, and improved memory efficiency a lot. But there was still a ceiling:
parsing and module resolution still depended on TypeScript APIs designed for
IDEs and language servers — not for the kind of single-pass static analysis Knip
does.

Starting today, Knip v6 parses your source files with oxc-parser. This is
more than just a parser swap for the sake of using the latest 'n greatest.

Knip has always been designed to parse each file only once, but the TypeScript
backend carried the overhead of wiring up an entire program along with the
typechecker. That's useful for IDEs keeping symbols connected, but much less so
when you only need to traverse an AST once to collect imports and exports. The
TypeScript backend made the setup as a whole harder and slower than it needed to
be, especially to keep large monorepos in check.

Now with TypeScript itself Go-ing places, replacing that backend was only a
matter of time.

Unsurprisingly, the search didn't take long: oxc-parser offers everything we
need and its (experimental) raw transfer is crazy fast. Massive props to
overlookmotel, Boshen and all contributors for all the work on the
oxc suite
!

Performance tuning

Next to this major refactor, I've been having a ball tuning Knip's performance
further. One thing to highlight here is that a few more plugins have been
refactored to statically analyze configuration files directly, as opposed to
actually importing them (including transitive dependencies...). This includes
the ESLint ("flat config"), tsdown and tsup plugins.

The numbers

Comparing v5 and v6 in some projects using Knip, all boosts are in the 2-4x
range:

venz-chart

Trust me, I could look at this chart all day long! The same numbers in a table:

Project v5.88.0 v6.0.0
astro 4.0s 2.0s
query 3.8s 1.7s
rolldown 3.7s 1.7s
sentry 11.0s 4.0s
TypeScript 3.7s 0.9s

What's new

  • Did I already mention Knip got 2-4x faster?
  • Support for TS namespaces (and modules), new issue type namespaceMembers:
ts
export namespace MyNamespace {
  export const myName = 'knip'; // we were ignored in v5,
  export type MyType = string; // yet in v6 we are included
}

Breaking changes

Granted, most of you won't even notice. Here's the list:

  • Dropped support for Node.js v18 → Knip v6 requires Node.js v20.19.0 or newer
  • Dropped issue type classMembers
  • Dropped --include-libs → this is now the default and only behavior
  • Dropped --isolate-workspaces → this is now the default and only behavior
  • Dropped --experimental-tags → use --tags
  • In reporter functions, issues.files is consistent with other issue
    shapes. Removed issues._files.
  • In the JSON reporter, issues are consistently arrays for any issue type.
    Removed root files.

Editor Extensions

Editor extensions benefit from the core upgrades, for being faster and
more memory-efficient. Regardless of new extension releases, the local version
of Knip will be detected and used. Upgrade knip in your dependencies when
you're ready.

What about classMembers?

I feel you. Even Knip itself was using it. Until today.

The problem is that the implementation relies on the JS-based
ts.LanguageService API that exposes the findReferences method. TypeScript v6
is the last JS-based release, and TypeScript v7 is a full rewrite in Go. I am
left wondering if it ever will be feasible and practical to build such features
using primitives (i.e. not via LSP) in a JS-based CLI (references:
microsoft/typescript-go#455, @typescript/api). Knip was already
pretty unique for even trying this in a CLI tool.

Not that many projects seem to be using it either: github.com search for
"classMembers path:knip.json"
.

If your project relies on it, feel free to open an issue on GitHub or contact me
and maybe we can work something out. Maybe a separate dedicated tool could work,
or extended support for Knip v5.

Upgrade today

sh
npm install -D knip@latest

Deep closing thoughts...

Remember, Knip it before you ship it! Have a great day ☀️


Packages Docs Src Content Docs Blog Slim Down To Speed Up (`packages/docs/src/content/docs/blog/slim-down-to-speed-up.mdx`)


title: Slim down to speed up
date: 2023-12-14
authors:

  • lars
    tags:
  • Update

tl;dr; Memory usage is up to 50% lower, runs are up to 60% faster and you
can start using v4 canary today. No "unused class members" for the time being,
but this feature is planned to be restored.

{/* excerpt */}

Introduction

Honestly, performance has always been a challenge for Knip. A longstanding
bottleneck has finally been eliminated and Knip is going to be a lot faster.
Skip straight to the bottom to install v4 canary and try it out! Or grab
yourself a nice drink and read on if you're interested in where we are coming
from, and where we are heading.

Projects & Workspaces

From the start, Knip has relied on TypeScript for its robust parser for
JavaScript and TypeScript files. And on lots of machinery important to Knip,
like module resolution and accurately finding references to exported values.
Parts of it can be customized, such as the (virtual) file system and the module
resolver.

In TypeScript terms, a "project" is like a workspace in a monorepo. Same as each
workspace has a package.json, each project has a tsconfig.json. The
ts.createProgram() method is used to create a program based on a
tsconfig.json and the machinery starts to read and parse source code files,
resolve modules, and so on.

Up until v2, when Knip wanted to find unused things in a monorepo, all programs
for all workspaces were loaded into memory. Workspaces often depend on each
other, so Knip couldn't load one project, analyze it and dispose it. This way,
connections across workspaces would be lost.

Shared Workspaces

Knip v2 said goodbye to this approach and implemented its own TypeScript backend
(after using ts-morph for this). Based on the compatibility of
compilerOptions, workspaces were merged into shared programs whenever
possible. Having less programs in memory led to significant performance
improvements. Yet ultimately it was still a stopgap, since everything was still
kept in memory for the duration of the process.

"Why does everything need to stay in memory?", you may wonder. The answer is
that Knip uses findReferences at the end of the process. Knip relied on this
TypeScript Language Server method for everything that's not easy to find. More
about that later in the story of findReferences

Serialization

Fortunately, everything that's imported and exported from source files
(including things like members of namespaces and enums) can be found relatively
easily during AST traversal. This way, references to exports don't have to be
"traced back" later on.

It's mostly class members that are harder to find due to their dynamic nature.
Without these, all information can be serialized for storage and retrieval (in
memory or on disk). Slimming down by taking class members out of the equation
simplifies things a lot and paves the way for all sorts of improvements.

We Have To Slim Down

The relevant part in the linting process can be summarized in 5 steps:

  1. Collect entry files and feed them to TypeScript
  2. Read files, resolve modules, and create ASTs
  3. Traverse ASTs and collect imports & exports
  4. Match exports against imports to determine what's unused
  5. Find references to hard-to-find exported values and members

If we would hold on to reporting unused class members, then especially steps 2
and 5 are hard to decouple. The program and the language service containing the
source files used to eventually trace back references can't really be decoupled.
So class members had to go. Sometimes you have to slim down to keep moving. One
step back, two steps forward.

If you rely on this feature, fear not. I plan to bring it back before the final
v4, but possibly behind a flag.

What's In Store?

So with this out of the way, everything becomes a lot clearer and we can finally
really start thinking about significant memory and performance improvements. So
what's in store here? A lot!

  • We no longer need to keep everything in memory, so workspaces are read and
    disposed in isolation, one at a time. Memory usage will be spread out more
    even. This does not make it faster, but reducing "out of memory" issues is
    definitely a Good Thing™️ in my book.
  • Knip could recover from unexpected exits and continue from the last completed
    workspace.
  • The imports and exports are in a format that can be serialized for storage and
    retrieval. This opens up interesting opportunities, such as local caching on
    disk, skipping work in subsequent runs, remote caching, and so on.
  • Handling workspaces in isolation and serialization result in parallelization
    becoming a possibility. This becomes essential, as module resolution and AST
    creation and traversal are now the slowest parts of the process and are not
    easy to optimize significantly (unless perhaps switching to e.g Rust).
  • No longer relying on findReferences speeds up the export/import matching
    part part significantly. So far I've seen improvements of up to 60% on total
    runtime
    , and my guess is that some larger codebases may profit even more.
  • The serialization format is still being explored and there is no caching yet,
    but having the steps more decoupled is another Good Thing™️ that future me
    should be happy about.

Back It Up, Please

I heard you. Here's some example data. You can get it directly from Knip using
the --performance flag when running it on any codebase. Below we have some
data after linting the Remix monorepo.

Knip v3

sh
$ knip --performance

Name                           size  min     max      median   sum
-----------------------------  ----  ------  -------  -------  -------
findReferences                  223    0.55  2252.35     8.46  5826.95
createProgram                     2   50.78  1959.92  1005.35  2010.70
getTypeChecker                    2    5.04   667.45   336.24   672.48
getImportsAndExports            396    0.00     7.19     0.11   104.46

Total running time: 9.7s (mem: 1487.39MB)

Knip v4

sh
$ knip --performance

...

Name                           size  min     max      median   sum
-----------------------------  ----  ------  -------  -------  -------
createProgram                     2   54.36  2138.45  1096.40  2192.81
getTypeChecker                    2    7.40   664.83   336.12   672.23
getImportsAndExports            396    0.00    36.36     0.16   224.37
getSymbolAtLocation            2915    0.00    29.71     0.00    65.63

Total running time: 4.3s (mem: 729.67MB)

Takeaways

The main takeaways here:

  • In v3,findReferences is where Knip potentially spends most of its time
  • In v4, total running time is down over 50%
  • In v4, memory usage is down 50% (calculated using
    process.memoryUsage().heapUsage)
  • In v4, getImportsAndExports is more comprehensive to compensate for the
    absence of findReferences - more on that below

Remember, unused class members are no longer reported by default in v4.

The story of `findReferences`

Did I mention Knip uses findReferences...? Knip relied on it for everything
that's not easy to find. Here's an example of an export/import match that is
easy to find:

ts title="import.ts"
import { MyThing } from './thing.ts';
ts title="export.ts"
export const MyThing = 'cool';

In v2 and v3, Knip collects many of such easy patterns. Other patterns are
harder to find with static analysis. This is especially true for class members.
Let's take a look at the next example:

ts title="myclass.ts"
class MyClass {
  constructor() {
    this.method();
  }
  method() {}
  do() {}
}

export const OtherName = MyClass;
ts title="instance.ts"
import * as MyNamespace from './MyClass.ts';

const { OtherName } = MyNamespace;

const instance = new OtherName();

instance.do();

Without a call or new expression to instantiate OtherName, its method
member would not be used (since the constructor would not be executed). To
figure this out using static analysis goes a long way. Through export
declarations, import declarations, aliases, initializers, call expressions...
the list goes on and on. Yet all this magic is exactly what happens when you use
"Find all references" or "Go to definition" in VS Code.

Knip used findReferences extensively, but it's what makes a part of Knip
rather slow. TypeScript needs to wire things up (through
ts.createLanguageService and program.getTypeChecker) before it can use this,
and then it tries hard to find all references to anything you throw at it. It
does this very well, but the more class members, enum members and namespaced
imports your codebase has, the longer it inevitably takes to complete the
process.

Besides letting go of class members, a slightly more comprehensive AST traversal
is required to compensate for the absence of findReferences (it's the
getImportsAndExports function in the metrics above). I'd like to give you an
idea of what "more comprehensive" means here.

In the following example, referencedExport was stored as export from
namespace.ts, but it was not imported directly as such:

ts title="namespace.ts"
export const referencedExport = () => {};
ts title="index.ts"
import * as NS from './namespace.ts';

NS.referencedExport();

Previously, Knip used findReferences() to "trace back" the usage of the
exported referencedExport.

The gist of the optimization is to pre-determine all imports and exports. During
AST traversal of index.ts , Knip sees that referencedExport is attached to
the imported NS namespace, and stores that as an imported identifier of
namespace.ts. When matching exports against imports, this lookup comes at no
extra cost. Additionally, this can be stored as strings, so it can be serialized
too. And that means it can be cached.

Knip already did this for trivial cases as shown in the first example of this
article. This has now been extended to cover more patterns. This is also what
needs to be tested more extensively before v4 can be released. Its own test
suite and the projects in the integration tests are already covered so we're
well on our way.

For the record, findReferences is an absolute gem of functionality provided by
TypeScript. Knip is still backed by TypeScript, and tries to speed things up by
shaking things off. In the end it's all about trade-offs.

Let's Go!

You can start using Knip v4 today, feel free to try it out! You might find a
false positive that wasn't there in v3, please report this.

sh
npm install -D knip@canary

Remember, Knip it before you ship it! Have a great day ☀️


Packages Docs Src Content Docs Blog State Of Knip (`packages/docs/src/content/docs/blog/state-of-knip.mdx`)


title: The State of Knip
date: 2025-02-28
authors:

  • lars
    tags:
  • Update

Honestly, Knip was a bit of a "cursed" project from the get-go. Getting anywhere
near a level of being broadly-ish valuable requires a good amount of
foolishness determination, and it has always been clear it would stay far
from perfect. It's telling that most of similar projects have been
abandoned.

And even though Knip is in its infancy, this update is meant as a sign we feel
we're still on to something. External indicators include increased usage looking
at numbers such as dependent repositories on GitHub and weekly downloads on npm,
and bug reports about increasingly less rudimentary issues.

{/* excerpt */}

Two Cases

For those interested, let's take a look at two cases that hopefully give an
impression of how Knip works under the hood and the level of issues we're
currently dealing with. It's assumed you already have a basic understanding of
Knip (otherwise please consider to read at least entry files and
plugins first).

Case 1: Next.js

Let's say this default configuration represents, greatly simplified, the
default entry patterns
for projects using Next.js:

json
{
  "next": {
    "entry": ["next.config.ts", "src/pages/**/*.tsx"]
  }
}

Those files will be searched for and then statically analyzed to collect
import statements and find other local files and external dependencies. This
is the generic way Knip handles all source files.

However, the game changes if the project uses the following Next.js
configuration:

ts title="next.config.ts"
const nextConfig = {
  pageExtensions: ['page.tsx'],
};

export default nextConfig;

Next.js will now look for files matching src/pages/**/*.page.tsx instead (note
the subtle change of the glob pattern). Knip should respect this to find used
and unused files properly.

Moving the burden to users for them to either not notice at all and get
incorrect results, or having to override the next.entry patterns and include
src/pages/**/*.page.tsx isn't good DX. Knip should take care of it.

To get the configuration object and the value of pageExtensions, Knip has to
actually load and execute next.config.ts ¹... and trouble is right around the
corner:

ts title="next.config.ts"
const nextConfig = {
  pageExtensions: ['page.tsx'],
  env: {
    BASE_URL: process.env.BASE_URL.toLowerCase(),
  },
};

export default nextConfig;
shell
$ knip
💥 LoaderError: Error loading next.config.ts
💥 Reason: Cannot read properties of undefined (reading 'toLowerCase')

Obviously a contrived example, but the gist is that lots of tooling
configuration expects environment variables to be defined. But when running Knip
there might not be a mechanism to set those. Clearly a breaking change when Knip
starts doing this, only for Next.js projects with a configuration file that
doesn't read environment variables safely (or has other contextual
dependencies).

By the way, the ESLint v9 plugin has a similar issue.

¹ Another approach could be to statically analyze the next.config.ts
configuration file. That would require some additional efforts and get us only
so far, but is definitely useful in some cases and on the radar.

EDIT: This has been solved in the Next.js plugin in v5.48.0.

Case 2: Knip does that?!

To further bring down user configuration and the number of false positives, the
system required more components. New components have been introduced to keep
improving and nail it for an increasing number of projects. This case is an
illustration of some of those components.

Let's just dive into this example and find out what's happening:

json title="package.json"
{
  "scripts": {
    "test": "yarn --cwd packages/frontend vitest -c vitest.components.config.ts"
  }
}

Orchestration is necessary between various components within Knip, such as:

  • Plugins, the Vitest plugin parses vitest.components.config.ts
  • Custom CLI argument parsing for executables, e.g. yarn --cwd [dir] and
    vitest --config [file]
  • The workspace graph, to see packages/frontend is a descendant workspace of
    the root workspace

Patterns like in the script above do not occur only in package.json files, but
could be anywhere. Here's a similar example in a GitHub Actions workflow:

yaml title=".github/workflows/test.yml"
jobs:
  integration:
    runs-on: ubuntu-latest
    steps:
      - run: playwright test -c playwright.e2e.config.ts
        working-directory: e2e

The pattern is very similar, because Knip needs to assign a configuration file
to a specific workspace (assuming there's one in ./e2e) and apply the Vitest
configuration to that particular workspace with its own set of directory and
entry file patterns.

An essential part of Knip is to build up the module graph for source files. With
the configuration files still in mind, this is the pattern Knip follows towards
this goal:

  • Find configuration files at default and custom locations
  • Assign them to the right workspace
  • Run plugins in their own workspace to take entry file patterns from the
    configuration objects
  • Load and parse configuration files to get referenced dependencies

The referenced dependencies are stored in the DependencyDeputy class to
eventually determine what dependencies are unused or missing in package.json
in each workspace.

Both the configuration and entry files are then used to start building up the
module graph.

Comprehensive

Discussing the two cases briefly covers only part of the whole process. This
might give a sense of the reason why Knip is pretty comprehensive. After all,
building the module graph for internal source files to find unused files and
exports requires the list of external dependencies including internal
workspaces. And on the other hand, a complete module graph is required to find
unused or missing external dependencies.

The comprehensiveness also requires a range of components in the system, such as
the aforementioned ones, compilers for popular frameworks and a script
parser
, and other affordances such as auto-fix.

That said, code organization could be improved to make it more accessible for
contributions and, for instance, expose programmatic APIs to use the generated
module graph outside of Knip. Additionally, existing plugins can better take
advantage of existing components in the system, and new plugins can be developed
to further reduce user configuration and false positives.

The End

That's all for today, thanks for reading! Have a great one, and don't forget:
Knip it before you ship it! ✂️


Packages Docs Src Content Docs Blog Two Years (`packages/docs/src/content/docs/blog/two-years.mdx`)


title: Two Years
date: 2024-10-04
authors:

  • lars
    tags:
  • Update

import EmojiBlastButton from '../../../components/EmojiBlastButton.astro';
import Projects from '../../../components/Projects.astro';
import Sponsors from '../../../components/Sponsors.astro';

Exactly two years ago the first commit was pushed to GitHub and the first
version of Knip was published to the npm registry. The name was initially
Exportman! We've come a loooong way... The JavaScript ecosystem is highly
dynamic and I've been crazy enough to even start, try and keep up with it! But
here we are.

{/* excerpt */}

October 4th is World Animal Day, so there was really no choice but bring in the
crazy mascot that early adopters may remember:

Crazy cow with orange scissors in Van Gogh style

Today we celebrate an unknown but CRAZY amount of clutter removed from so many
codebases with Knip's help. Every single day I see many of those little red
blocks for thousands of lines of deleted code and dependencies. Call me crazy,
but to me this is pure joy and never gets old!    🟩 🟥 🟥 🟥 🟥

Smiling faces

The actual amount of code and dependencies removed and the number of smiling
faces this brings is what matters most, but also remain a good mystery. Clearly
more and more projects add Knip to their projects and CI workflows to keep
ever-growing codebases tidy. It's wonderful to see if Knip plays its part in
today's ecosystem to help with that. Thanks for bearing with me, here's to a lot
more little red blocks in your PRs!    🟩 🟥 🟥 🟥 🟥

Updates

Why not throw in some freshly cooked updates in v5.31.0 for you while we're
at it:

  • The auto-fix feature has been completely revamped, it's much better and a
    lot more comprehensive! You have to see it to believe it.
  • Knip has upgraded to Jiti v2, resolving a bunch of known issues when
    loading configuration files authored in TypeScript and ESM, such as:
text
Cannot use 'import.meta' outside a module
await is only valid in async functions and the top level bodies of modules
Unexpected identifier 'Promise'
Reflect.metadata is not a function

And that pesky "CJS build of Vite's Node API is deprecated" warning is finally
gone!

Thanks to everyone involved in making this happen, it's truly much appreciated.

Stable

If you haven't tried Knip recently, it's worth taking another look! Version 5
was released 8 months ago, and even though there were no breaking changes, it
includes many enhancements. In fact, Knip has been largely stable since version
3, which came out a year ago. Many releases have a compound effect, as Knip has
kept the pace for two years now.

Projects using Knip

This list of projects using Knip to keep their codebases tidy is something I
couldn't be more proud of:

:::section{.columns.min200}

:::

And so many more on and off the radar. Very, very cool!

Sponsors

Last but not least, eternal gratitude for all the sponsors that have been
supporting me along the way. THANK YOU, THANK YOU, THANK YOU!

And eh.. gotta take my chances: how about joining this awesome club?

:::section{.columns.min300.mt}

:::

Acknowledgements

Thanks to Joshua Goldberg for emoji-blast! 🎉


Packages Docs Src Content Docs Explanations Comparison And Migration (`packages/docs/src/content/docs/explanations/comparison-and-migration.md`)


title: Comparison & Migration

First of all, Knip owes a lot to the projects on this page and they've all been
inspirational in their own way. For best results, Knip has a vision embracing
comprehensiveness
which is larger in scope than any of the alternatives. So
if any of those tools has the right scope for your requirements, then by all
means, use what suits you best. Note that most projects are no longer
maintained.

All tools have in common that they have less features and don't support the
concept of monorepos/workspaces. Feel free to send in projects that Knip
does not handle better, Knip loves to be challenged!

Migration

A migration consists of deleting the dependency and its configuration file and
getting started with Knip. You should end up with less configuration.

Comparison

depcheck

Depcheck is a tool for analyzing the dependencies in a project to see:
how each dependency is used, which dependencies are useless, and which
dependencies are missing from package.json.

The project has plugins (specials), yet not as many as Knip has and they're not
as advanced. It also supports compilers (parsers) for non-standard files.

The following commands are similar:

sh
depcheck
knip --dependencies

Project status: The project is archived and recommends Knip.

unimported

Find and fix dangling files and unused dependencies in your JavaScript
projects.

unimported is fast and works well. It works in what Knip calls "production
mode" exclusively. If you're fine with a little bit of configuration and don't
want or need to deal with non-production items (such as devDependencies and
test files), then this might work well for you.

The following commands are similar:

sh
unimported
knip --production --dependencies --files

Project status: The project is archived and recommends Knip.

ts-prune

Find unused exports in a typescript project. 🛀

ts-prune aims to find potentially unused exports in your TypeScript project
with zero configuration.

The following commands are similar:

sh
ts-prune
knip --include exports,types,nsExports,nsTypes

Use knip --exports to also include enum and namespace members.

Project status: The project is archived and recommends Knip.

ts-unused-exports

ts-unused-exports finds unused exported symbols in your Typescript
project

The following commands are similar:

sh
ts-unused-exports
knip --include exports,types,nsExports,nsTypes

Use knip --exports to also include enum and namespace members.

tsr

Remove unused code from your TypeScript Project

tsr (previously ts-remove-unused) removes unused exports, and works based
on a single tsconfig.json file (includes and excludes) and requires no
configuration. It removes the export keyword or the whole export declaration.

Project status: The project is archived and recommends Knip.

Additional alternative and related projects include:

In general, the e18e.dev website and in particular the Cleanup
section is a great resource when dealing with technical debt.


Packages Docs Src Content Docs Explanations Entry Files (`packages/docs/src/content/docs/explanations/entry-files.md`)


title: Entry Files

Entry files are the starting point for Knip to determine what files are used in
the codebase. More entry files lead to increased coverage of the codebase. This
also leads to more dependencies to be discovered. This page explains how Knip
and its plugins try to find entry files so you don't need to configure them
yourself.

Default entry file patterns

For brevity, the default configuration on the previous page mentions only
index.js and index.ts, but the default set of file names and extensions is
actually a bit larger:

  • index, main and cli
  • js, mjs, cjs, jsx, ts, mts, cts and tsx

This means files like main.cjs and src/cli.ts are automatically added as
entry files. Here's the default configuration in full:

json
{
  "entry": [
    "{index,cli,main}.{js,cjs,mjs,jsx,ts,cts,mts,tsx}",
    "src/{index,cli,main}.{js,cjs,mjs,jsx,ts,cts,mts,tsx}"
  ],
  "project": ["**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}!"]
}

Next to the default locations, Knip looks for entry files in other places. In
a monorepo, this is done for each workspace separately.

The values you set override the default values, they are not merged.

Plugins

Plugins often add entry files. For instance, the Remix, Storybook and Vitest
plugins add additional entry files. See the next page about plugins for
more details about this.

Scripts in package.json

The package.json is scanned for entry files. The main, bin, and exports
fields may contain entry files. The scripts are also parsed to find entry
files and dependencies. See Script Parser for more details.

Ignored files

Knip respects .gitignore files. By default, ignored files are not added as
entry files. This behavior can be disabled by using the --no-gitignore
flag on the CLI.

Configuring project files

See configuring project files for guidance on tuning entry and project
and when to use ignore.

Where Knip looks for entry files

Putting it together, Knip discovers entry files from:

  • Default locations such as index.js and src/index.ts
  • The main, bin and exports fields in package.json
  • Entry and config files added by enabled plugins (the config files are entry
    files too)
  • Dynamic imports: require() and import()
  • require.resolve() and import.meta.resolve()
  • new URL('./file.js', import.meta.url)
  • new Worker(…) and child_process fork/spawn/execFile when the target
    is path.join(__dirname, …)
  • module.register('./loader.js') loader registration
  • Scripts the script parser extracts: package.json scripts, CI workflow
    run commands, and source-code calls (tagged $ templates,
    node:child_process, execa, nano-spawn)

Entry files are added to the module graph, and module resolution may pull in
further entry files recursively until none remain.


Packages Docs Src Content Docs Explanations How Knip Works (`packages/docs/src/content/docs/explanations/how-knip-works.md`)


title: How Knip works

Knip finds and removes unused code and dependencies. It starts at your entry
files, follows every import to the files they reach, and builds a graph of
everything connected. Anything left out is reported as unused: a file no entry
reaches, an export no file imports, a dependency no file uses.

This page explains how Knip produces its results.

Build and analyze

Knip runs in two phases.

The build phase starts from the entry files. Knip reads a file, parses it,
collects its imports and exports, resolves each import to another file, and
repeats for every newly reached file. The result is a graph of the project:
every reachable file, what it imports, and what imports it.

Resolution is broad by design. Knip follows custom path aliases and extensions,
reads references out of config files and shell scripts, and "compiles"
non-standard files like .vue and .svelte so their imports are in the graph
too.

The analysis phase queries that graph. It never re-parses anything. It walks the
edges already recorded:

  • a file is unused when no entry reaches it
  • an export is unused when no other file imports it
  • a dependency is unused when no file imports it (and unlisted when a file
    imports it but package.json doesn't list it)

Analysis happens by quering the graph. Your results are only as accurate as the
graph is complete. And the graph depends on your entry files.

Entry files decide what Knip can see

The graph contains only what's reachable from an entry file. Miss one entry, and
everything reachable only from it looks unused.

Ideally you don't need to list entries by hand. Knip starts from sensible
defaults like src/index.ts, and plugins automatically add the entry
points specific to your frameworks and libraries. The Vitest plugin adds your
test files, the Astro plugin adds your pages, and so on. This is why plugins are
important: each one tells Knip about a whole class of files it might otherwise
never reach.

Findings come in chains

Unused results cascade. They don't appear in isolation. Say a tool generates an
entry file Knip doesn't know about:

  • that file looks unused, because nothing reaches it
  • every file it imports looks unused too, because nothing else reaches them
  • every export in those files looks unused
  • every dependency only those files used looks unused

One unreached entry can turn into dozens of findings. The chain also runs in
reverse: add the entry, and the whole cascade collapses at once.

So read the output from the root down: unused files first, then exports, then
dependencies.
A single unused file usually explains a long list of results
below it. Fix causes at the root and the rest disappears.

Some chains only show up one run at a time. An export counts as used when
another file imports it, even if the importing code is unused. Remove that code,
and the next run may report the export. Unused exports that import each other
across files are never reported.

A surprising result might be a gap, not a bug

When Knip flags something you're sure is used, it's based on the graph: it
couldn't reach that code from an entry. The cause is usually one of:

  • a missing entry for a tool or convention Knip doesn't know yet
  • a dynamic import Knip can't follow statically, or a path it can't resolve
  • a transitive dependency of a package that resolves only because something else
    installs it

In each case the fix is to teach Knip: add the entry, configure the plugin, list
the dependency. Don't hide the result. Configuring project files and
resolving reported issues cover how, issue type by issue type.


Packages Docs Src Content Docs Explanations Plugins (`packages/docs/src/content/docs/explanations/plugins.md`)


title: Plugins

This page describes why Knip uses plugins and the difference between config
and entry files.

Knip has an extensive and growing list of built-in plugins. Feel free to
write a plugin so others can benefit too!

What does a plugin do?

Plugins are enabled if the related package is listed in the list of dependencies
in package.json. For instance, if astro is listed in dependencies or
devDependencies, then the Astro plugin is enabled. And this means that this
plugin will:

Configuration files

Knip uses entry files as starting points to scan your source code and
resolve other internal files and external dependencies. The module graph can be
statically resolved through the require and import statements in those
source files. However, configuration files reference external dependencies in
various ways. Knip uses a plugin for each tool to parse configuration files and
find those dependencies.

Example: ESLint

In the first example we look at the ESLint plugin. The default config
file patterns include .eslintrc.json. Here's a minimal example:

json title=".eslintrc.json"
{
  "extends": ["airbnb", "prettier"],
  "plugins": ["@typescript-eslint"]
}

Configuration files like this don't import or require anything, but they do
require the referenced dependencies to be installed.

In this case, the plugin will return three dependencies:

  • eslint-config-airbnb
  • eslint-config-prettier
  • @typescript-eslint/eslint-plugin

Knip will then look for missing dependencies in package.json and report those
as unlisted. And vice versa, if there are any ESLint plugins listed in
package.json, but unused, those will be reported as well.

Example: Vitest

The second example uses the Vitest plugin. Here's a minimal example of a
Vitest configuration file:

ts title="vitest.config.ts"
import { defineConfig } from 'vitest/config';

export default defineConfig({
  test: {
    coverage: {
      provider: 'istanbul',
    },
    environment: 'happy-dom',
  },
});

The Vitest plugin reads this configuration and returns two dependencies:

  • @vitest/coverage-istanbul
  • vitest-environment-happy-dom

Knip will look for missing and unused dependencies in package.json and report
accordingly.

Some tools allow configuration to be stored in package.json, that's why some
plugins contain package.json in the list of config files.

:::tip[Summary]

Plugins load configuration files to find referenced dependencies, and determine
unused and unlisted dependencies.

:::

Entry files

Many plugins have default entry files configured. When the plugin is enabled,
Knip will add entry files as configured by the plugin to resolve used files and
dependencies.

For example, if next is listed as a dependency in package.json, the Next.js
plugin will automatically add multiple patterns as entry files, such as
pages/**/*.{js,jsx,ts,tsx}. If vitest is listed, the Vitest plugin adds
**/*.{test,test-d,spec,spec-d}.ts as entry file patterns. Most plugins have
entry files configured, so you don't have to.

It's mostly plugins for meta frameworks and test runners that have entry files
configured.

:::tip[Plugins result in less configuration]

Plugins use entry file patterns as defined in your configuration file of these
tools. So you don't need to repeat this in your Knip configuration.

:::

For example, let's say your Playwright configuration contains the following:

ts title="playwright.config.ts"
import type { PlaywrightTestConfig } from '@playwright/test';

const config: PlaywrightTestConfig = {
  testDir: 'integration',
  testMatch: ['**/*-test.ts'],
};

export default config;

The Playwright plugin will read this configuration file and return those entry
patterns (integration/**/*-test.ts). Knip will then not use the default entry
patterns.

You can still override this behavior in your Knip configuration:

json title="knip.json"
{
  "playwright": {
    "entry": "src/**/*.integration.ts"
  }
}

This should not be necessary though. Please consider opening a pull request or a
bug report if any plugin is not behaving as expected.

:::tip[Summary]

Plugins try hard to automatically add the correct entry files.

:::

Entry files from config files

Entry files are part of plugin configuration (as described in the previous
section). Yet plugins can also return additional entry files after parsing
configuration files. Below are some examples of configuration files parsed by
plugins to return additional entry files. The goal of these examples is to give
you an idea about the various ways Knip and its plugins try to find entry files
so you don't need to configure them yourself.

Angular

The Angular plugin parses the Angular configuration file. Here's a fragment:

json title="angular.json"
{
  "$schema": "./node_modules/@angular/cli/lib/config/schema.json",
  "projects": {
    "knip-angular-example": {
      "architect": {
        "build": {
          "builder": "@angular-devkit/build-angular:browser",
          "options": {
            "outputPath": "dist/knip-angular-example",
            "main": "src/main.ts",
            "tsConfig": "tsconfig.app.json"
          }
        }
      }
    }
  }
}

This will result in src/main.ts being added as an entry file (and
@angular-devkit/build-angular as a referenced dependency).

Additionally, the Angular plugin returns tsconfig.app.json as a configuration
file for the TypeScript plugin.

GitHub Actions

This plugin parses workflow YAML files. This fragment contains three run
scripts:

yml title=".github/workflows/deploy.yml"
jobs:
  integration:
    runs-on: ubuntu-latest
    steps:
      - run: npm install
      - run: node scripts/build.js
      - run: node --loader tsx scripts/deploy.ts
      - run: playwright test -c playwright.web.config.ts
        working-directory: e2e

From these scripts, the scripts/build.js and scripts/deploy.ts files will be
added as entry files by the GitHub Actions plugin.

Additionally, the file e2e/playwright.web.config.ts is detected and will be
handed over as a Playwright configuration file.

Read more about this in command-line arguments.

webpack

Let's take a look at this example webpack configuration file:

js title="webpack.config.js"
module.exports = env => {
  return {
    entry: {
      main: './src/app.ts',
      vendor: './src/vendor.ts',
    },
    module: {
      rules: [
        {
          test: /\.(woff|ttf|ico|woff2|jpg|jpeg|png|webp)$/i,
          use: 'base64-inline-loader',
        },
      ],
    },
  };
};

The webpack plugin will parse this and add ./src/app.ts and ./src/vendor.ts
as entry files. It will also add base64-inline-loader as a referenced
dependency.

:::tip[Summary]

In your config files, plugins can find additional entry files and also other
config files recursively.

:::

Bringing it all together

Sometimes a configuration file is a JavaScript or TypeScript file that imports
dependencies, but also contains configuration that needs to be parsed by a
plugin to find additional dependencies.

Let's take a look at this example Vite configuration file:

ts title="vite.config.ts"
import { defineConfig } from 'vite';
import react from '@vitejs/plugin-react';

export default defineConfig(async ({ mode, command }) => {
  return {
    plugins: [react()],
    test: {
      setupFiles: ['./setup-tests.ts'],
      environment: 'happy-dom',
      coverage: {
        provider: 'c8',
      },
    },
  };
});

This file imports vite and @vitejs/plugin-react directly, but also
indirectly references the happy-dom and @vitest/coverage-c8 packages.

The Vite plugin of Knip will dynamically load this configuration file and
parse the exported configuration. But it's not aware of the vite and
@vitejs/plugin-react imports. This is why such config files are also
automatically added as entry files for Knip to statically resolve the
import and require statements.

Additionally, ./setup-tests.ts will be added as an entry file.

:::note

When plugins dynamically load configuration files, conditional dependencies may
not be detected if the condition evaluates differently during analysis. See
conditional or dynamic dependencies for details and workarounds.

:::

Command-Line Arguments

Plugins may define the arguments where Knip should look for entry files,
configuration files and dependencies. We've already seen some examples above:

sh
node --loader tsx scripts/deploy.ts
playwright test -c playwright.web.config.ts

Please see script parser for more details.

Config File Location

If configuration files aren't in their default location and they are not
referenced through some script like vite -c ./dir/vite.config.ts, then make
sure to tell Knip about it. Two examples:

json title="knip.jsonc"
{
  "playwright": { "config": ["e2e/playwright.config.ts"] },
  "vite": "packages/*/vite.config.ts" // shorthand without `config` and array notation
}

This is common in projects where a directory like packages/lib is not an
actual workspace with a package.json file. Also see integrated monorepos
for similar cases.

Summary

:::tip[Summary]

Plugins are configured with two distinct types of files:

  • config files are dynamically loaded and parsed by the plugin
  • entry files are added to the module graph
  • Both can recursively lead to additional entry files, config files and
    dependencies

:::


Packages Docs Src Content Docs Explanations Why Use Knip (`packages/docs/src/content/docs/explanations/why-use-knip.md`)


title: Why use Knip?

The value of removing clutter is clear, but finding it manually is tedious. Meet
Knip: comprehensive and accurate results at any scale.

:::tip[TL;DR]

Unused files, exports and dependencies pile up as projects grow. Removing them
improves maintenance, performance, security and onboarding, and prevents
regressions in CI. Knip automates finding (and fixing) all three,
comprehensively and at any scale.

:::

Less is more

There are plenty of reasons to delete unused files, dependencies and "dead
code":

  • Easier maintenance: things are easier to manage when there's less of it.
  • Improved performance: startup time, build time and/or bundle size can be
    negatively impacted when unused code, files and/or dependencies are included.
    Relying on tree-shaking when bundling code helps, but it's not a silver
    bullet.
  • Increased security: less code and dependencies means less room for weak spots.
  • Easier onboarding: there should be no doubts about whether files, dependencies
    and exports are actually in use or not. Especially for people new to the
    project and/or taking over responsibilities this is harder to grasp.
  • Prevent regressions: tools like TypeScript, ESLint and Prettier do all sorts
    of checks and linting to report violations and prevent regressions. Knip does
    the same for dependencies, exports and files that are obsolete.
  • Keeping dead code around has a negative value on readability, as it can be
    misleading and distracting. Even if it serves no purpose it will need to be
    maintained (source: Safe dead code removal → YAGNI).
  • Also see Why are unused dependencies a problem? and Why are unused
    exports a problem?
    .

Automation

Code and dependency management is usually not the most exciting task for most of
us. Knip's mission is to automate finding clutter. This is such a tedious job if
you were to do it manually, and where would you even start? Knip applies many
techniques and heuristics to report what you need and save a lot of time.

:::tip

Knip not only finds clutter, it can also remove clutter!

Use Knip next to a linter like ESLint or Biome: after removing unused variables
inside files, Knip might find even more unused code. Rinse and repeat!

:::

Comprehensive

You can use alternative tools that do the same. However, the advantage of a
strategy that addresses all of dependencies, exports and files is in their
synergy:

  • Utilizing plugins to find their dependencies includes the capacity to find
    additional entry and configuration files. This results in more resolved and
    used files. Better coverage gives better insights into unused files and
    exports.
  • Analyzing more files reveals more unused exports and dependency usage,
    refining the list of both unused and unlisted dependencies.
  • This approach is amplified in a monorepo setting. In fact, files and
    internal dependencies can recursively reference each other (across
    workspaces).

Knip reaches this coverage through fine-grained entry points, custom
module resolution
, configuration-file parsers, an advanced
shell-script parser
, and built-in and custom compilers, then fixes
most of it automatically
.

Greenfield or Legacy

Installing Knip in greenfield projects ensures the project stays neat and tidy
from the start. Add it to your CI workflow and prevent any regressions from
entering the codebase.

:::tip

Use Knip in a CI environment to prevent future regressions.

:::

In large and/or legacy projects, Knip may report false positives and require
some configuration. It aims to be a great assistant when cleaning up parts of
the project or doing large refactors. Even a list of results with a few false
positives is many times better and faster than if you were to do it manually.

Unobtrusive

Knip does not introduce new syntax for you to learn. This may sound obvious, but
consider comments like the following:

js
// eslint-disable-next-line
// prettier-ignore
// @ts-expect-error

Knip doesn't have similar comments like // knip-ignore so you can get rid of
false positives for a variety of reasons:

  1. A false positive may be a bug in Knip, and should be reported, not dismissed.
  2. Instead of proprietary comments, use standardized annotations that also
    serve as documentation.
  3. In the event you want to remove Knip, just uninstall knip without having to
    remove useless comments scattered throughout the codebase.

Tip: use @lintignore in JSDoc comments, so other linters can use the same.


Packages Docs Src Content Docs Features Auto Fix (`packages/docs/src/content/docs/features/auto-fix.mdx`)


title: Auto-fix

import { Tabs, TabItem } from '@astrojs/starlight/components';
import { Badge } from '@astrojs/starlight/components';

Run Knip as you normally would, and if the report looks good then run it again
with the --fix flag to let Knip automatically apply fixes. It fixes the
following issue types:

  • Remove unused files
  • Remove export keyword for unused exports, re-exports, and exported types
  • Remove export default keywords for unused default exports
  • Remove unused enum and namespace members
  • Remove unused dependencies and devDependencies from package.json
  • Remove unused catalog entries

:::caution

Use a VCS (version control system) like Git to review and undo changes as
necessary.

:::

Flags

Fix

Add the --fix flag to remove unused exports and dependencies:

sh
knip --fix

Add --allow-remove-files to allow Knip to remove unused files:

sh
knip --fix --allow-remove-files

Use --fix-type to fix only specific issue types:

  • dependencies
  • exports
  • types
  • files
  • catalog

Example:

sh
knip --fix-type exports,types
knip --fix-type exports --fix-type types   # same as above

Format

Add --format to format the modified files using the formatter and
configuration in your project. Supports Biome, deno fmt, dprint and Prettier
(using Formatly):

sh
knip --fix --format

Demo

Post-fix

After Knip has fixed issues, there are four things to consider:

1. Use a formatter

Use a tool like Prettier or Biome if the code needs formatting. Knip removes the
minimum amount of code while leaving it in a working state.

:::tip

Add the --format flag to format the modified files using the formatter and
configuration in your project.

:::

2. Unused variables

Use a tool like ESLint or Biome to find and remove unused imports and variables
inside files. Even better, try remove-unused-vars to remove unused
variables within files.

This may result in more deleted code, and Knip may then find more unused code.
Rinse and repeat!

3. Unused dependencies

Verify changes in package.json and update dependencies using your package
manager.

```shell npm install ``` ```shell pnpm install ``` ```shell bun install ``` ```shell yarn ```

4. Install unlisted dependencies

If Knip reports unlisted dependencies or binaries, they should be installed
using the package manager in the project, for example:

```shell npm install unlisted-package ``` ```shell pnpm add unlisted-package ``` ```shell bun add unlisted-package ``` ```shell yarn add unlisted-package ```

Example results

Exports

The export keyword for unused exports is removed:

diff title="module.ts"
-export const unused = 1;
-export default class MyClass {}
+const unused = 1;
+class MyClass {}

The default keyword was also removed here.

Knip removes the whole or part of export declarations:

diff title="module.ts"
type Snake = 'python' | 'anaconda';
const Owl = 'Hedwig';
const Hawk = 'Tony';
-export type { Snake };
-export { Owl, Hawk };
+;
+;

Re-exports

Knip removes the whole or part of re-exports:

diff title="file.js"
-export { Cat, Dog } from './pets';
-export { Lion, Elephant } from './jungle';
+export { Elephant } from './jungle'

Also across any chain of re-exports:

```diff export const Hawk = 'Tony'; -export const Owl = 'Hedwig'; +const Owl = 'Hedwig'; ``` ```diff export * from './module.js'; ``` ```diff -export { Hawk, Owl } from './barrel.js'; +export { Hawk } from './barrel.js' ```

Export assignments

Knip removes individual exported items in "export assignments", but does not
remove the entire export declaration if it's empty:

diff title="file.js"
-export const { a, b  } = fn();
+export const {   } = fn();

-export const [c, d] = [c, d];
+export const [, ] = [c, d];

Reason: the right-hand side of the assignment might have side-effects. It's not
safe to always remove the whole declaration. This could be improved in the
future (feel free to open an issue/RFC).

Enum members

Unused members of enums are removed:

diff title="file.ts"
export enum Directions {
  North = 1,
  East = 2,
-  South = 3,
  West = 4,
}

CommonJS

Knip supports CommonJS and removes unused exports:

diff title="common.js"
-module.exports = { identifier, unused };
+module.exports = { identifier,  };

-module.exports.UNUSED = 1;
-module.exports['ACCESS'] = 1;
+
+

Warning: the right-hand side of such an assignment might have side-effects. Knip
currently removes the whole declaration (feel free to open an issue/RFC).

Dependencies

Unused dependencies are removed from package.json:

diff title="package.json"
{
   "name": "my-package",
   "dependencies": {
-    "rimraf": "*",
-    "unused-dependency": "*"
+    "rimraf": "*"
   },
-  "devDependencies": {
-    "unreferenced-package": "5.3.3"
-  }
+  "devDependencies": {}
 }

Catalog entries

Unused catalog entries are removed from pnpm-workspace.yaml:

diff title="pnpm-workspace.yaml"
packages:
   - 'packages/*'
 catalog:
   react: ^18.0.0
-  unused-package: ^1.0.0

Catalogs in package.json are supported as well.

What's not included

Operations that auto-fix does not (yet) perform and why:

  • Add unlisted (dev) dependencies to package.json (should it go into
    dependencies or devDependencies? For monorepos in current workspace or
    root?)
  • Add unlisted binaries (which package and package version contains the used
    binary?)
  • Fix duplicate exports (which one should be removed?)

Packages Docs Src Content Docs Features Catalogs (`packages/docs/src/content/docs/features/catalogs.md`)


title: Catalogs

Catalogs let you define dependency version ranges once and reference them across
workspaces in a monorepo. Knip reports catalog entries that are defined but no
longer referenced, and references to entries that do not exist. It can remove
unused entries with auto-fix.

Supported catalogs

Knip reads catalogs from the first applicable location:

  • pnpm-workspace.yaml — the catalog (default) and catalogs (named) keys
  • .yarnrc.yml — the catalog and catalogs keys
  • package.json — the catalog and catalogs keys
  • package.json#workspaces — the catalog and catalogs keys (Bun)

Unused catalog entries

A catalog entry is reported as unused when no workspace references it through
the catalog: protocol in its package.json:

json title="packages/app/package.json"
{
  "dependencies": {
    "react": "catalog:",
    "zod": "catalog:validation"
  }
}

catalog: references the default catalog, while catalog:validation
references the named validation catalog. References are resolved from
dependencies, devDependencies, peerDependencies, optionalDependencies,
resolutions, pnpm-workspace.yaml#overrides, and pnpm.overrides in the root
package.json (pnpm 10 and older).

Entries defined in a catalog but not referenced anywhere are reported as unused
catalog entries
.

Unresolved catalog references

A catalog: reference is reported when the selected catalog does not define
that package. These issues point to the consuming package.json or
pnpm-workspace.yaml, where package managers would otherwise fail to resolve
the version.

Filter and fix

The catalog and catalogReferences issue types are included by the
--dependencies shortcut. Focus on them (or exclude them) like any other
issue type:

sh
knip --include catalog,catalogReferences
knip --exclude catalog,catalogReferences

Auto-fix removes unused catalog entries from the catalog file:

sh
knip --fix --fix-type catalog

Packages Docs Src Content Docs Features Compilers (`packages/docs/src/content/docs/features/compilers.md`)


title: Compilers

Projects may have source files that are not JavaScript or TypeScript, and thus
require compilation (or transpilation, or pre-processing, you name it). Files
like .mdx, .astro, .vue and .svelte may also import other source files
and external dependencies. So ideally, these files are included when linting the
project. That's why Knip supports compilers.

Built-in compilers

Knip has built-in "compilers" for the following file extensions:

  • .astro
  • .css (only enabled by tailwindcss)
  • .mdx
  • .prisma
  • .sass + .scss
  • .less
  • .styl + .stylus
  • .svelte
  • .tsrx
  • .vue

Knip does not include real compilers for those files, but regular expressions to
collect import statements. This is fast, requires no dependencies, and enough
for Knip to build the module graph. The tradeoff is that unused exports inside
these files go unreported.

Real compilers recover those exports, but bring their own challenges. The Svelte
compiler, for instance, keeps exports intact even when they represent
component properties, so Knip reports them as unused. When you need that
fidelity, override any built-in with the framework's own compiler (see the
Svelte and Vue examples below). For example, to report unused exports
in .tsrx files.

Compilers are enabled only when a related dependency is found. The .tsrx
compiler when a @tsrx/* package is installed, for example. If that detection
doesn't work for your project, set true to enable any compiler manually:

ts title="knip.ts"
export default {
  compilers: {
    mdx: true,
  },
};

Compiled files and `project` patterns

The default project patterns include compiler extensions automatically. When
you override project, list them explicitly:

ts title="knip.ts"
export default {
  project: ['src/**/*.{ts,tsx,css}'],
};

Files outside project are not analyzed, so their imports don't count. A
narrowed pattern like ['src/**/*.{ts,tsx}'] silently drops everything a
stylesheet imports, which is a common source of false positives: @import 'tailwindcss' in an excluded .css file gets tailwindcss reported as an
unused dependency. Knip reports a configuration hint when a compiled extension
is excluded this way.

Custom compilers

Built-in compilers can be overridden, and additional compilers can be added.
Since compilers are functions, the Knip configuration file must be a dynamic
.js or .ts file.

Interface

The compiler function interface is straightforward. Text in, text out:

ts
(source: string, filename: string) => string | PromiseLike<string>;

Return a string or a Promise-like value that resolves to a string. Knip compiles
files on demand and awaits asynchronous results; the async keyword is
optional.

Examples

CSS

Here's an example, minimal compiler for CSS files:

ts title="knip.ts"
export default {
  compilers: {
    css: (text: string) => [...text.matchAll(/(?<=@)import[^;]+/g)].join('\n'),
  },
};

You may wonder why the CSS compiler is not included by default. It's currently
not clear if it should be included. And if so, what would be the best way to
determine it should be enabled, and what syntax(es) it should support. Note that
Tailwind CSS, SASS/SCSS, Less and Stylus compilers are included.

MDX

Another example, in case the built-in MDX compiler is not enough:

ts
import { compile } from '@mdx-js/mdx';

export default {
  compilers: {
    mdx: async text => (await compile(text)).toString(),
  },
};

Svelte

In a Svelte project, the compiler is automatically enabled. Override and use
Svelte's compiler for better results if the built-in "compiler" is not enough:

ts
import type { KnipConfig } from 'knip';
import { compile } from 'svelte/compiler';

export default {
  compilers: {
    svelte: (source: string) => compile(source, {}).js.code,
  },
} satisfies KnipConfig;

Vue

In a Vue project, the compiler is automatically enabled. Override and use Vue's
parser for better results if the built-in "compiler" is not enough:

ts
import type { KnipConfig } from 'knip';
import {
  parse,
  type SFCScriptBlock,
  type SFCStyleBlock,
} from 'vue/compiler-sfc';

function getScriptBlockContent(block: SFCScriptBlock | null): string[] {
  if (!block) return [];
  if (block.src) return [`import '${block.src}'`];
  return [block.content];
}

function getStyleBlockContent(block: SFCStyleBlock | null): string[] {
  if (!block) return [];
  if (block.src) return [`@import '${block.src}';`];
  return [block.content];
}

function getStyleImports(content: string): string {
  return [...content.matchAll(/(?<=@)import[^;]+/g)].join('\n');
}

const config = {
  compilers: {
    vue: (text: string, filename: string) => {
      const { descriptor } = parse(text, { filename, sourceMap: false });
      return [
        ...getScriptBlockContent(descriptor.script),
        ...getScriptBlockContent(descriptor.scriptSetup),
        ...descriptor.styles.flatMap(getStyleBlockContent).map(getStyleImports),
      ].join('\n');
    },
  },
} satisfies KnipConfig;

export default config;

Tracking non-source files

To track any file extension that's not meant to compile to JavaScript, register
a no-op compiler:

ts title="knip.ts"
export default {
  compilers: { template: () => '' },
};

Packages Docs Src Content Docs Features Custom Elements (`packages/docs/src/content/docs/features/custom-elements.md`)


title: Custom Elements

Custom elements are registered under a tag name. When such a class isn't
referenced elsewhere, Knip would report the export as unused. Instead, Knip
recognizes the registration and considers the class used.

Native registration needs no configuration. Framework registrations are credited
through their plugin, which is enabled when the framework is a dependency.

Native registration

The web standard customElements.define() credits the registered class:

ts
export class MyCard extends HTMLElement {}

customElements.define('my-card', MyCard);

The registry can be the global customElements — directly, through a window,
globalThis or self prefix, or a local alias — a CustomElementRegistry
instance, or a shadow root's scoped registry:

ts
const registry = new CustomElementRegistry();
registry.define('my-card', MyCard);

A class can also register itself from a static block using this:

ts
export class MyCard extends HTMLElement {
  static {
    customElements.define('my-card', this);
  }
}

Framework registration

Framework Plugin Registration Imported from
Lit lit @customElement('tag') lit/decorators, lit-element, @lit/reactive-element
FAST fast @customElement or .define() @microsoft/fast-element
Stencil stencil @Component({ tag }) @stencil/core
Catalyst catalyst @controller @github/catalyst

The decorator counts only when imported from the framework. A locally defined or
differently-imported decorator is ignored.

Lit

A @customElement('tag') decorator credits the class when the matching plugin
is enabled:

ts
import { LitElement } from 'lit';
import { customElement } from 'lit/decorators.js';

@customElement('my-card')
export class MyCard extends LitElement {}

FAST

FAST also registers elements through a static define() or defineAsync()
method. A class extending FASTElement, directly or through a mixin, is
credited when registered this way:

ts
import { FASTElement } from '@microsoft/fast-element';

export class MyCard extends FASTElement {}

MyCard.define('my-card');

Stencil

A @Component decorator credits the class:

ts
import { Component } from '@stencil/core';

@Component({ tag: 'my-card' })
export class MyCard {}

Catalyst

The bare @controller decorator registers the element under a tag derived from
the class name:

ts
import { controller } from '@github/catalyst';

@controller
export class MyCardElement extends HTMLElement {}

Packages Docs Src Content Docs Features Integrated Monorepos (`packages/docs/src/content/docs/features/integrated-monorepos.md`)


title: Integrated Monorepos

Some repositories have a single package.json, but consist of multiple projects
with configuration files across the repository. A good example is the Nx
integrated monorepo style
.

:::tip

An integrated monorepo is a single workspace.

:::

Entry Files

The default entrypoints files might not be enough. Here's an idea that might fit
this type of monorepo:

json title="knip.json"
{
  "entry": ["{apps,libs}/**/src/index.{ts,tsx}"],
  "project": ["{apps,libs}/**/src/**/*.{ts,tsx}"]
}

Plugins

Let's assume some of these projects are applications ("apps") which have their
own ESLint configuration files and Cypress configuration and test files. This
may result in those files getting reported as unused, and consequently also the
dependencies they import and refer to.

In that case, we could configure the ESLint and Cypress plugins like this:

json title="knip.json"
{
  "eslint": {
    "config": ["{apps,libs}/**/.eslintrc.json"]
  },
  "cypress": {
    "entry": ["apps/**/cypress.config.ts", "apps/**/cypress/e2e/*.spec.ts"]
  }
}

Adapt the file patterns to your project, and the relevant config and entry
files and dependencies should no longer be reported as unused.

Internal Workspace Dependencies

A note about repositories with multiple package.json files and internal
workspace packages: it is recommended to list all dependencies in each consuming
package.json, allowing Knip to do fine-grained reporting of both unused and
unlisted dependencies.

An alternative is to ignoreDependencies: ["@internal/*"].


Packages Docs Src Content Docs Features Monorepos And Workspaces (`packages/docs/src/content/docs/features/monorepos-and-workspaces.md`)


title: Monorepos & Workspaces

Workspaces are handled out-of-the-box by Knip.

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.

Configuration

Here's example configuration with custom entry and project patterns:

json title="knip.json"
{
  "workspaces": {
    ".": {
      "entry": "scripts/*.js",
      "project": "scripts/**/*.js"
    },
    "packages/*": {
      "entry": "{index,cli}.ts",
      "project": "**/*.ts"
    },
    "packages/cli": {
      "entry": "bin/cli.js"
    }
  }
}

:::tip

Run Knip without any configuration to see if and where custom entry and/or
project files are necessary per workspace.

:::

Each workspace has the same default configuration.

The root workspace is named "." under workspaces (like in the example
above).

:::caution

In a project with workspaces, the entry and project options at the root
level are ignored. Use the workspace named "." for those (like in the example
above).

:::

Workspaces

Knip reads workspaces from four possible locations:

  1. The workspaces array in package.json (npm, Bun, Yarn, Lerna)
  2. The packages array in pnpm-workspace.yaml (pnpm)
  3. The workspaces.packages array in package.json (legacy)
  4. The workspaces object in Knip configuration

The workspaces in Knip configuration (4) not already defined in the root
package.json or pnpm-workspace.yaml (1, 2, 3) are added to the analysis.

:::caution

A workspace must have a package.json file.

:::

For projects with only a root package.json, please see integrated
monorepos
.

Additional workspaces

If a workspace is not configured as such in package.json#workspaces (or
pnpm-workspace.yaml) it can be added to the Knip configuration manually. Add
their path to the workspaces configuration object the same way as
"packages/cli": {} in the example above.

Source mapping

See Source Mapping.

Additional options

The following options are available inside workspace configurations:

Plugins can be configured separately per workspace.

Use --debug for verbose output and see the workspaces Knip includes, their
configurations, enabled plugins, glob options and resolved files.

Filter workspaces

Use the --workspace (or -W) argument to select one or more workspaces:

sh
knip --workspace packages/my-lib

The filter supports multiple formats:

sh
knip --workspace @myorg/my-lib     # Package name
knip --workspace '@myorg/*'        # Package name glob
knip --workspace packages/my-lib   # Directory path
knip --workspace './apps/*'        # Directory glob

Combine selectors to include or exclude workspaces:

sh
knip --workspace @myorg/* --workspace '!@myorg/legacy'
knip --workspace './apps/*' --workspace '@shared/utils'

This will include the target workspace(s), but also ancestor, dependency and
dependent workspaces. For three reasons:

  • Ancestor workspaces may list dependencies in package.json the linted
    workspace uses.
  • Dependency workspaces may provide configuration and source information used by
    the linted workspace.
  • Dependent workspaces may reference exports from the linted workspace.

To lint the workspace in isolation, there are two options:


Packages Docs Src Content Docs Features Production Mode (`packages/docs/src/content/docs/features/production-mode.md`)


title: Production Mode

The default mode for Knip is comprehensive and targets all project code,
including configuration files, test files, Storybook stories, and so on. Test
files usually import production files. This prevents production files or their
exports from being reported as unused, while sometimes both of them can be
deleted. Knip features a "production mode" to focus only on the code that you
ship.

Configuration

To tell Knip what is production code, add an exclamation mark behind each
pattern! that represents production code:

json title="knip.json"
{
  "entry": ["src/index.ts!", "build/script.js"],
  "project": ["src/**/*.ts!", "build/*.js"]
}

Depending on file structure and enabled plugins, you might not need to modify
your configuration at all.

Run Knip with the --production flag:

sh
knip --production

Here's what's included in production mode:

  • Only entry and project patterns suffixed with !
  • Only production entry file patterns exported by plugins (such as Next.js and
    Remix)
  • Only the start script (of package.json#scripts)
  • Ignore exports with the @internal tag

:::note

The production run does not replace the default run. Depending on your needs you
can run either of them or both separately. Usually both modes can share the same
configuration.

:::

To see the difference between default and production mode in great detail, use
the --debug flag and inspect what entry and project files are used, and the
plugins that are enabled. For instance, in production mode this shows that files
such as tests and Storybook files (stories) are excluded from the analysis.

In case files like mocks and test helpers are reported as unused files, use
negated patterns to exclude those files in production mode:

json title="knip.json"
{
  "entry": ["src/index.ts!"],
  "project": ["src/**/*.ts!", "!src/test-helpers/**!"]
}

Also see configuring project files to align entry and project with
production mode.

Strict Mode

In production mode, only dependencies (not devDependencies) are considered
when finding unused or unlisted dependencies.

Additionally, the --strict flag can be added to:

sh
knip --production --strict

Using --strict implies --production, so the latter can be omitted.

Types

Add --exclude types if you don't want to include types in the report:

sh
knip --production --exclude types

Packages Docs Src Content Docs Features Reporters (`packages/docs/src/content/docs/features/reporters.md`)


title: Reporters & Preprocessors

Built-in Reporters

Knip provides the following built-in reporters:

Example usage:

sh
knip --reporter compact

CodeClimate

The built-in codeclimate reporter generates output in the Code Climate Report
JSON format. Example usage:

text
$ knip --reporter codeclimate

[
  {
    "type": "issue",
    "check_name": "Unused exports",
    "description": "isUnused",
    "categories": ["Bug Risk"],
    "location": {
      "path": "path/to/file.ts",
      "positions": {
        "begin": {
          "line": 6,
          "column": 1
        }
      }
    },
    "severity": "major",
    "fingerprint": "e9789995c1fe9f7d75eed6a0c0f89e84"
  }
]

CODEOWNERS

When a .github/CODEOWNERS file exists, each entry gains an owners array.
Point the reporter at a different path through --reporter-options:

sh
knip --reporter json --reporter-options '{"codeowners":"docs/CODEOWNERS"}'

Cycles

A verbose, multi-line tree view of circular dependencies. Each file path
is suffixed with the location of the import that continues the cycle. Each edge
shows the import kind and specifier, descends one file per level, and closes
(↩) back to the file it started from.

Use --cycles to report only circular dependencies and --reporter cycles to
display them as a tree:

text
$ knip --cycles --reporter cycles

Circular dependencies (1)

src/i18n/index.ts:1:28
└── import ./middleware → src/i18n/middleware.ts:3:15
    └── re-export ../core/i18n/handler → src/core/i18n/handler.ts:5:10
        └── import ../../i18n → src/i18n/index.ts:1:28 ↩

Knip reports representative cycle paths found while walking the module graph.
This is not an exhaustive list of every possible simple cycle in a cyclic
subgraph, because that can produce a noisy and very large report. If the same
file participates in multiple distinct reported paths, each path is shown
separately.

Use cycles.allow to accept known cycle paths.

Dynamic imports are ignored by default because they are commonly used to avoid
synchronous circular loads. Set cycles.dynamicImports to include them.
Repeated starting imports are grouped under one heading.

For one line per cycle path, use knip --cycles with the default reporter. To
report unused code as well, combine the issue-type shorthands.

Cycles are warnings by default. Set "rules": { "cycles": "error" } to make
reported cycles cause a non-zero exit code.

Disclosure

This reporter is useful for sharing large reports. Groups of issues are rendered
in a closed state initially. The reporter renders this:

text
$ knip --reporter disclosure

<details>
<summary>Unused files (2)</summary>

```
unused.ts
dangling.js
```

</details>

<details>
<summary>Unused dependencies (2)</summary>

```
my-package     package.json:17:5
unused-dep     package.json:20:5
```

</details>

The above can be copy-pasted where HTML and Markdown is supported, such as a
GitHub issue or pull request, and renders like so:

Unused files (2)

text
unused.ts
dangling.js

Unused dependencies (2)

text
my-package     package.json:17:5
unused-dep     package.json:20:5

GitHub Actions

Use the GitHub Actions reporter in a workflow for annotations in pull requests.
Example usage:

sh
knip --reporter github-actions

Changed files in pull requests will now contain inline annotations for lint
findings.

JSON

The json reporter prints machine-readable results for scripts, CI, and tools
(including coding agents) to consume:

sh
knip --reporter json

Output is one line of JSON. Formatted here for readability:

json
{
  "issues": [
    {
      "file": "src/legacy.ts",
      "files": [{ "name": "src/legacy.ts" }]
    },
    {
      "file": "src/math.ts",
      "exports": [{ "name": "factorial", "line": 12, "col": 14, "pos": 256 }],
      "types": [{ "name": "Radians", "line": 20, "col": 13, "pos": 410 }]
    },
    {
      "file": "package.json",
      "dependencies": [{ "name": "lodash" }],
      "unlisted": [{ "name": "rimraf" }]
    }
  ]
}

The top level is an object with a single issues array. Each element groups
every issue found in one file:

Field Type Notes
file string Path relative to the working directory
owners { name }[] Code owners, only when a CODEOWNERS file is found
issue type array One key per enabled issue type (see below)

Each entry carries a key for every enabled issue type, so the keys are
the same across entries. An array is empty when that file has no issues of that
type. Drop a type's key by disabling it with filters or rules.

Issue-type items are objects with position info:

Field Type Notes
name string The unused symbol, dependency, file, or import
namespace string? Set for namespace members
line number? 1-based line
col number? 1-based column
pos number? Character offset

See Issue types for the full set of issue-type keys.

For a typed object instead of JSON to parse, write a custom reporter.
Coding agents can also call Knip through the MCP server, which returns
structured results and configuration hints directly.

SARIF

The sarif reporter emits SARIF 2.1.0 for code-scanning integrations:

sh
knip --reporter sarif > knip.sarif

For example, upload the report to GitHub Code Scanning after installing project
dependencies:

yaml
permissions:
  contents: read
  security-events: write

steps:
  - uses: actions/checkout@v4
  - run: pnpm exec knip --reporter sarif > knip.sarif
  - if: always()
    uses: github/codeql-action/upload-sarif@v4
    with:
      sarif_file: knip.sarif

The upload step runs even when Knip finds issues and exits non-zero. GitHub Code
Scanning availability for private repositories depends on the repository's
GitHub Code Security plan.

Markdown

The built-in markdown reporter output is meant to be saved to a Markdown file.
This allows following the changes in issues over time. It reports issues in
Markdown tables separated by issue types as headings, for example:

md
# Knip report

## Unused files (1)

- src/unused.ts

## Unlisted dependencies (2)

| Name            | Location          | Severity |
| :-------------- | :---------------- | :------- |
| unresolved      | src/index.ts:8:23 | error    |
| @org/unresolved | src/index.ts:9:23 | error    |

## Unresolved imports (1)

| Name         | Location           | Severity |
| :----------- | :----------------- | :------- |
| ./unresolved | src/index.ts:10:12 | error    |

Custom Reporters

When the provided built-in reporters are not sufficient, a custom local reporter
can be implemented or an external reporter can be used. Multiple reporters can
be used at once by repeating the --reporter argument.

The results are passed to the function from its default export and can be used
to write issues to stdout, a JSON or CSV file, or sent to a service. It
supports a local JavaScript or TypeScript file or an external dependency.

Local

The default export of the reporter should be a function with this interface:

ts
type Reporter = (options: ReporterOptions) => void;

type ReporterOptions = {
  report: Report;
  issues: Issues;
  counters: Counters;
  configurationHints: ConfigurationHints;
  isDisableConfigHints: boolean;
  isTreatConfigHintsAsErrors: boolean;
  cwd: string;
  isProduction: boolean;
  isShowProgress: boolean;
  options: string;
};

The data can then be used to write issues to stdout, a JSON or CSV file, or
sent to a service.

Here's a most minimal reporter example:

ts title="./my-reporter.ts"
import type { Reporter } from 'knip';

const reporter: Reporter = function (options) {
  console.log(options.issues);
  console.log(options.counters);
};

export default reporter;

Example usage:

sh
knip --reporter ./my-reporter.ts

External

Pass --reporter [pkg-name] to use an external reporter. The default exported
function of the main script (default: index.js) will be invoked with the
ReporterOptions, just like a local reporter.

Preprocessors

A preprocessor is a function that runs after the analysis is finished. It
receives the results from the analysis and should return data in the same
shape/structure (unless you pass it to only your own reporter).

The data goes through the preprocessors before the final data is passed to the
reporters. There are no built-in preprocessors. Just like reporters, use e.g.
--preprocessor ./my-preprocessor from the command line (can be repeated).

The default export of the preprocessor should be a function with this interface:

ts
type Preprocessor = (options: ReporterOptions) => ReporterOptions;

Like reporters, you can use local JavaScript or TypeScript files and external
npm packages as preprocessors.

Example preprocessor:

ts title="./preprocess.ts"
import type { Preprocessor } from 'knip';

const preprocess: Preprocessor = function (options) {
  // modify options.issues and options.counters
  return options;
};

export default preprocess;

Example usage:

sh
knip --preprocessor ./preprocess.ts

Preprocessors can also be configured in the Knip configuration file:

json title="knip.json"
{
  "preprocessor": "./preprocess.ts"
}

Packages Docs Src Content Docs Features Rules And Filters (`packages/docs/src/content/docs/features/rules-and-filters.md`)


title: Rules & Filters

Use rules or filters to customize Knip's output. This has various use cases, a
few examples:

  • Temporarily focus on a specific issue type.
  • You don't want to see unused type, interface and enum exports reported.
  • Specific issue types should be printed, but not counted against the total
    error count.

If you're looking to handle one-off exceptions, also see JSDoc tags.

Filters

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:

sh
knip --include files --include dependencies
knip --include files,dependencies

Or the other way around, use --exclude to ignore the types you're not
interested in:

sh
knip --include files --exclude enumMembers,duplicates

The nsExports and nsTypes types are off by default. Including only
those adds them to the default report, rather than narrowing it to just those.

cycles is also off by default. --include cycles (or "include": ["cycles"]
in configuration) reports only circular dependencies. Use it with the cycles
reporter
for a detailed tree view.

Also see the list of issue types.

Shorthands

Knip has shortcuts to report only specific issue types.

  1. The --dependencies flag includes:

    • dependencies (and devDependencies + optionalPeerDependencies)
    • unlisted
    • binaries
    • unresolved
    • catalog
    • catalogReferences
  2. The --exports flag includes:

    • exports
    • types
    • enumMembers
    • namespaceMembers
    • duplicates
  3. The --files flag is a shortcut to report only unused files.

  4. The --cycles flag is a shortcut to report only circular dependencies.

Combine shorthands to report the default issue types plus circular dependencies:

sh
knip --dependencies --exports --files --cycles

Rules

Use rules in the configuration to customize the issue types that count towards
the total error count, or to exclude them altogether.

Value Default Printed Counted Description
"error" ✓ ¹ ✓ ✓ Similar to the --include filter
"warn" - ✓ - Printed in faded/gray color
"off" - - - Similar to the --exclude filter

Example:

json title="knip.json"
{
  "rules": {
    "files": "warn",
    "duplicates": "off"
  }
}

Notes:

  • ¹ Exception: the cycles issue type is a warning by default.
  • If the dependencies issue type is included, the devDependencies and
    optionalPeerDependencies types can still be set to "warn" separately.
  • The rules are modeled after the ESLint rules configuration, and could be
    extended in the future.

Also see the issue types overview.

Rules or filters?

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 shorthands (rules don't have this).

Packages Docs Src Content Docs Features Script Parser (`packages/docs/src/content/docs/features/script-parser.md`)


title: Script Parser

Knip parses shell commands and scripts to find additional dependencies, entry
files and configuration files in various places:

Shell scripts are parsed and statically analyzed, but they're not executed.

package.json

The main, bin, exports and scripts fields may contain entry files. Let's
take a look at this example:

json title="package.json"
{
  "name": "my-package",
  "main": "index.js",
  "exports": {
    "./lib": {
      "import": "./dist/index.mjs",
      "require": "./dist/index.cjs"
    }
  },
  "bin": {
    "program": "bin/cli.js"
  },
  "scripts": {
    "build": "rollup src/entry.ts",
    "start": "node --loader tsx server.ts"
  }
}

From this example, Knip automatically adds the following files as entry files:

  • index.js
  • ./dist/index.mjs
  • ./dist/index.cjs
  • bin/cli.js
  • src/entry.ts
  • server.ts

Excluded files

Knip would not add the exports if the dist folder is matching a pattern in a
relevant .gitignore file or ignore option.

Knip does not add scripts without a standard extension. For instance, the
bin/tool file might be a valid executable for Node.js, but wouldn't be added
or parsed by Knip.

CLI Arguments

When parsing the scripts of package.json and other files, Knip detects
various types of inputs. Some examples:

  • The first positional argument is usually an entry file
  • Configuration files are often in the -c or --config argument
  • The --require, --loader or --import arguments are often dependencies
json
{
  "name": "my-lib",
  "scripts": {
    "start": "node --import tsx/esm run.ts",
    "bundle": "tsup -c tsup.lib.config.ts",
    "type-check": "tsc -p tsconfig.app.json"
  }
}

The "start" script will have tsx marked as a referenced dependency, and adds
run.ts as an entry file.

Additionally, the following files are detected as configuration files:

  • tsup.lib.config.ts - to be handled by the tsup plugin
  • tsconfig.app.json - to be handled by the TypeScript plugin

Such executables and their arguments are all defined in plugins separately for
fine-grained results.

Scripts

Plugins may also use the script parser to extract entry files and dependencies
from commands. A few examples:

  • GitHub Actions: workflow files may contain run commands (e.g.
    .github/workflows/ci.yml)
  • Husky & Lefthook: Git hooks such as .git/hooks/pre-push contain scripts;
    also lefthook.yml has run commands
  • Lint Staged: configuration values are all commands
  • Nx: task executors and nx:run-commands executors in project.json contains
    scripts
  • Release It: hooks contain commands

Plugins can also return configuration files. Some examples:

  • The Angular plugin detects options.tsConfig as a TypeScript config file
  • The GitHub Actions plugin parses run commands which may contain
    configuration file paths

Source Code

When Knip is walking the abstract syntax trees (ASTs) of JavaScript and
TypeScript source code files, it looks for imports and exports. But there's a
few more (rather obscure) things that Knip detects in the process. Below are
examples of additional scripts Knip parses to find entry files and dependencies.

bun

If the bun dependency is imported in source code, Knip considers the contents
of $ template tags to be scripts:

ts
import { $ } from 'bun';
await $`bun boxen I ❤ unicorns`;
await $`boxen I ❤ unicorns`;

Parsing the script results in the boxen binary (the boxen-cli dependency) as
referenced (twice).

execa

If the execa dependency is imported in source code, Knip considers the
contents of $ template tags to be scripts:

ts
await $({ stdio: 'inherit' })`c8 node hydrate.js`;

Parsing the script results in hydrate.js added as an entry file and the c8
binary/dependency as referenced. The execa(), execaSync(), execaCommand()
and execaCommandSync() call forms are parsed the same way.

zx

If the zx dependency is imported in source code, Knip considers the contents
of $ template tags to be scripts:

ts
await $`node scripts/parse.js`;

This will add scripts/parse.js as an entry file.

child\_process

If node:child_process is imported in source code, Knip parses the command in
exec/execSync calls and the executable (with its arguments) in
spawn/spawnSync/execFile/execFileSync calls:

ts
import { execSync, execFile } from 'node:child_process';
execSync('eslint --cache');
execFile('boxen', ['I ❤ unicorns']);

This marks the eslint and boxen binaries as referenced. An inline
path.join(__dirname, …) argument to spawn, execFile or fork is added as
an entry file instead.

nano-spawn

If nano-spawn is imported in source code, Knip parses the executable and its
arguments:

ts
import spawn from 'nano-spawn';
await spawn('gsutil', ['cp', './file.txt', 'gs://bucket/']);

This marks the gsutil binary as referenced.


Packages Docs Src Content Docs Features Source Mapping (`packages/docs/src/content/docs/features/source-mapping.md`)


title: Source Mapping

Knip is mostly interested in source code. Analyzing build artifacts hurts
performance and often leads to false positives, as they potentially contain
bundled code and unresolvable imports.

That's why Knip tries to map such build artifacts back to their original source
files and analyze those instead. This is done based on tsconfig.json settings.

Example 1: package.json

Let's look at an example case with package.json and tsconfig.json files, and
see how "dist" files are mapped to "src" files.

jsonc title="package.json"
{
  "name": "my-workspace",
  "main": "index.js",
  "exports": {
    ".": "./src/entry.js",
    "./feat": "./lib/feat.js",
    "./public": "./dist/app.js",
    "./public/*": "./dist/*.js",
    "./public/*.js": "./dist/*.js",
    "./dist/internal/*": null,
  },
}

With this TypeScript configuration:

json title="tsconfig.json"
{
  "compilerOptions": {
    "baseUrl": "src",
    "outDir": "dist"
  }
}
  • ./src/entry.js is not in an outDir folder, so it's added as an entry file
  • ./lib/feat.js is not in an outDir folder, so it's added as an entry file
  • ./dist/app.js is in a dist folder and mapped to ./src/app.{js,ts} (¹)
  • ./dist/*.js is in a dist folder and mapped to ./src/**/*.{js,ts} (¹)
  • ./dist/internal/* is translated to ./dist/internal/** and files in this
    directory and deeper are ignored when globbing entry files

(¹) full extensions list is actually: js, mjs, cjs, jsx, ts, tsx,
mts, cts

In --debug mode, look for "Source mapping" to see this in action.

:::tip

Using ./dist/*.js means that all files matching ./src/**/*.{js,ts} are added
as entry files. By default, unused exports of entry files are not reported. Use
includeEntryExports to include them.

:::

Example 2: monorepo

Let's say we have this module in a monorepo that imports helper from another
workspace in the same monorepo:

ts title="index.js"
import { helper } from '@org/shared';

The target workspace @org/shared has this package.json:

json title="package.json"
{
  "name": "@org/shared",
  "main": "dist/index.js"
}

The module resolver will resolve @org/shared to dist/index.js. That file is
usually compiled and git-ignored, while Knip wants the source file instead.

:::tip

You may need to compile build artifacts to outDir first before Knip can
successfully apply source mapping for internal references in a monorepo.

:::

If the target workspace has a tsconfig.json file with an outDir option, Knip
will try to map the "dist" file to the "src" file. Then if src/index.ts
exists, Knip will use that file instead of dist/index.js.

Currently this only works based on tsconfig.json, in the future more source
mappings may be added.

Example 3: source conditions

Source-first monorepos may expose source files directly through a custom
condition in package.json#exports or #imports maps:

json title="package.json"
{
  "name": "@org/shared",
  "exports": {
    ".": {
      "@org/source": "./src/index.ts",
      "types": "./dist/index.d.ts",
      "default": "./dist/index.js"
    }
  }
}

When module resolution fails because the targets aren't built, Knip falls back
to the first conditional target of the workspace package that exists on disk,
regardless of the condition's name. No build step or configuration required.


Packages Docs Src Content Docs Guides Adopt Gradually (`packages/docs/src/content/docs/guides/adopt-gradually.md`)


title: Adopt Knip gradually

On a large or legacy codebase the first run can report a lot. Reaching zero in
one sitting is then the wrong goal. Get a trustworthy signal first, then ratchet
the codebase toward clean and keep it there.

Actionable baseline

Most of the noise on a first run comes from gaps in what Knip can reach: a
dependency that Knip does not have a plugin for yet, an entry point it can't
resolve, a phantom dependency. Fix the gaps before you delete anything:

Read the output from the top. One unused file hides its exports and
dependencies, so files come first.

Once the report reflects real findings instead of gaps, you have an actionable
baseline.

Focus one issue type at a time

Don't fix everything at once. Narrow the report with filters:

sh
knip --files        # unused files only (the major cascade!)
knip --dependencies # then dependencies
knip --exports      # then exports and types

Clear one type, commit, move to the next.

Stage by rules

Rules set a severity per issue type. Start by setting all types to
"warn", and then the types you've cleaned to "error". Warnings are printed
in gray, and they don't fail the run. Example:

json title="knip.json"
{
  "rules": {
    "files": "error",
    "dependencies": "error",
    "exports": "warn",
    "duplicates": "off"
  }
}

As each "warn" type reaches zero, set it to "error". Keep iterating and
prune the codebase to perfection.

Gate CI without blocking the team

Add Knip to CI as soon as you have a baseline, so new clutter is caught in
review. A few ways to stage it:

  • Mark cleaned types "error" and the rest "warn", so CI fails only on the
    types you've finished.
  • Workspace by workspace: in a monorepo, scope passes with --workspace
    (accepts multiple workspaces and glob patterns).
  • Production first: --production limits analysis to shipped code, so you
    can clean what matters most and defer tests and tooling.
  • Set a budget, so CI fails only when the count exceeds the number (e.g. knip --max-issues 50). This blocks regressions while you burn down the rest.
  • Run report-only with --no-exit-code while you're still triaging.

Suppress only what's justified

When something is genuinely fine as-is, prefer a specific, documented signal
over a blanket ignore, for example:

Resolving reported issues covers the right tool for each case.


Packages Docs Src Content Docs Guides Configuring Project Files (`packages/docs/src/content/docs/guides/configuring-project-files.md`)


title: Configuring Project Files

The entry and project file patterns are the first and most important
options. Getting those right is essential to help you delete more code and make
Knip faster:

  • Start with defaults. Only add targeted entry overrides when needed.
  • Use project patterns (with negations) to define the scope for Knip.
  • Use production mode to exclude tests and other non-production files.
  • Use ignore only to suppress issues in specific files; it does not exclude
    files from analysis.

Let's dive in and expand on all of these.

Entry files

Avoid adding too many files as entry files:

  1. Knip does not report unused exports in entry files by default.
  2. Proper entry and project patterns allow Knip to find unused files and
    exports.

Unused files

Files are reported as unused if they are in the set of project files, but are
not resolved from the entry files:

text
unused files = project files - (entry files + resolved files)

See entry files to see where Knip looks for entry files. Fine-tune entry
and adjust project to fit your codebase.

:::tip

Use negated project patterns to precisely include/exclude files for unused
files detection.

Use ignore* to suppress specific issues in matching files; it does not exclude
files from analysis.

:::

Negated patterns

When there are too many files in the analysis, start here.

For instance, routes are entry files except those prefixed with an underscore:

json
{
  "entry": ["src/routes/*.ts", "!src/routes/_*.ts"]
}

Some files are not part of your source and are reported as unused (false
positives)? Use negated project patterns:

json
{
  "entry": ["src/index.ts"],
  "project": ["src/**/*.ts", "!src/exclude/**"]
}

❌   Don't use ignore for generated artifacts:

json title="knip.json"
{
  "entry": ["src/index.ts", "scripts/*.ts"],
  "ignore": ["build/**", "dist/**", "src/generated.ts"]
}

✅   Do define your project boundaries:

json title="knip.json"
{
  "entry": ["src/index.ts", "scripts/*.ts"],
  "project": ["src/**", "scripts/**"],
  "ignore": ["src/generated.ts"]
}

Why this is better:

  • project defines what belongs to the codebase, so build outputs are excluded
    from analysis and unused file detection
  • ignore is for the few files that should be analyzed but contain exceptions
  • Improves performance by analyzing fewer files

Ignore issues in specific files

Use ignore when a specific analyzed file is not handled properly by Knip or
intentionally contains unused exports (e.g. generated files exporting
"everything"):

json
{
  "entry": ["src/index.ts"],
  "project": ["src/**/*.ts"],
  "ignore": ["src/generated.ts"]
}

Also see ignoreExportsUsedInFile for a more targeted approach.

Production Mode

Default mode includes tests and other non-production files in the analysis. To
focus on production code, use production mode.

Don't try to exclude tests via ignore or negated project patterns. That's
inefficient and ineffective due to entries added by plugins. Use production mode
instead.

❌   Don't do this:

json
{
  "ignore": ["**/*.test.js"]
}

Why not: ignore hides issues from the report; it does not exclude files from
analysis.

❌   Also don't do this:

json
{
  "entry": ["index.ts", "!**/*.test.js"]
}

Why not: plugins add test files as entry files; you can't and shouldn't
override that globally.

❌   Or this:

json
{
  "project": ["**/*.ts", "!**/*.spec.ts"]
}

Why not: project is for unused file detection; negating test files is
ineffective because they are entry files.

✅   Do this instead:

shell
knip --production

To fine-tune the resulting production file set, for instance to exclude test
helper files that still show as unused, use the exclamation mark suffix on
production patterns:

json
{
  "entry": ["src/index.ts!"],
  "project": ["src/**/*.ts!", "!src/test-helpers/**!"]
}

Remember to keep adding the exclamation mark suffix! for production file
patterns.

:::tip

Use the exclamation mark (!) on both ends (!) to exclude files in production
mode.

:::

Defaults & Plugins

To reiterate, the default entry and project files for each workspace:

json
{
  "entry": [
    "{index,cli,main}.{js,cjs,mjs,jsx,ts,cts,mts,tsx}",
    "src/{index,cli,main}.{js,cjs,mjs,jsx,ts,cts,mts,tsx}"
  ],
  "project": ["**/*.{js,cjs,mjs,jsx,ts,cts,mts,tsx}!"]
}

Next to this, there are other places where Knip looks for entry files.

Additionally, plugins have plenty of entry files configured that are
automatically added as well.


Packages Docs Src Content Docs Guides Contributing (`packages/docs/src/content/docs/guides/contributing.md`)


title: Contributing to Knip

Here are some ways to contribute to Knip:

Writing a plugin is a great and fun way to get started.

The CONTRIBUTING.md and DEVELOPMENT.md guides should get you up and
running quickly.

The main goal of Knip is to keep projects clean & tidy. Everything that
contributes to that goal is welcome!


Packages Docs Src Content Docs Guides Handling Issues (`packages/docs/src/content/docs/guides/handling-issues.mdx`)


title: Resolve reported issues

import { Tabs, TabItem } from '@astrojs/starlight/components';

When Knip reports something you didn't expect, it's telling the truth about its
module graph: it couldn't reach that code from an entry file. So a
surprising result is usually a real finding or a configuration gap, not a false
positive to silence. The fix is to teach Knip about your project, issue type by
issue type.

Work the types top-down. Findings come in chains, so reducing unused files also
reduces unused exports and dependencies:

  1. Unused files
  2. Unresolved imports
  3. Unused exports
  4. Unused dependencies

Reach for ignore* options only as a last resort, and never before reading
Configuring project files. Hiding a result is not the same as resolving it.

:::tip

Let your coding agent do this for you: the Knip Editor Extension ships an
MCP server that uses these docs to write knip.json for any project.

:::

Unused files

Getting the list of unused files right trickles down into the other issue types
as well, so we start here. Files are reported as unused if they are in the set
of project files, but not in the set of files resolved from the entry files:

text
unused files = project files - (entry files + resolved files)

:::tip

Addressing reported Configuration Hints first might help significantly,
especially when handling unused files.

:::

Common causes for unused files include:

In most cases you can add entry patterns manually.

Use --files to filter the report and focus only on unused files:

sh
knip --files

This works with other issue types as well. For instance, use --dependencies to
focus only on dependencies and exclude issues related to unused files and
exports.

:::caution

Don't add unused files to the ignore option before reading configuring
project files
. Learn why and when to use entry, project, production mode
and ignore* patterns for better results and performance.

:::

Missing generated files

For certain features, Knip needs to run after relevant files are generated. For
instance, source mapping in a monorepo may require files to be built into
dist folders first. And generated files in the src directory may import
other files. For instance, the src/routeTree.gen.ts file generated by
@tanstack/router must exist so Knip can find the imported route files.

Solution: compile and/or generate the relevant files first so Knip can
resolve and find the source files.

If Knip still reports false positives, you may need to strategically add an
entry file manually.

Dynamic import specifiers

Dynamic import specifiers aren't resolved, such as:

ts
const entry = await import(path.join(baseDir, 'entry.ts'));

Solution: add entry.ts to entry patterns.

Unsupported arguments in scripts

Some tooling command arguments aren't recognized:

json
{
  "name": "my-lib",
  "version": "1.0.0",
  "scripts": {
    "build": "unknown-build-cli --entry production.ts"
  }
}

Solution: add production.ts to entry patterns.

This works the same for any script, also those in GitHub Actions workflows or
Git hooks. See script parser for more details about Knip's script parser.

Unsupported file formats

Entry files referenced in HTML files (e.g. <script src="production.js">).

html
<html>
  <body>
    <script type="module" src="production.js"></script>
  </body>
</html>

Solution: add production.js to entry patterns. Or add an .html
compiler to extract and resolve the value of <script src> elements.

Knip has support for some popular framework formats through compilers, and
additional compilers can be added for any file type. The recommended solution is
usually to add the file as shown in each example as an entry file.

Missing plugin

You might be using a tool or framework for which Knip doesn't have a plugin
(yet). Configuration and entry files (and related dependencies) may be reported
as unused because there is no plugin yet that would include those files. Two
examples:

  • Configuration file tool.config.js contains a reference to the package
    "@tool/plugin" → both the file and the dependency are reported as unused.
  • A framework automatically imports all files matching src/models/*.ts → those
    files are reported as unused.

Solution: create a new plugin for the tool or framework that's not in
the list
yet. Or work around it and add entry patterns and maybe ignore a
dependency or two (using ignoreDependencies).

Incomplete plugin

Files may be reported as unused if existing plugins do not include that entry
file pattern yet. See the plugins section of entry files for more details.

Solution: override plugin configuration to customize default patterns
for existing plugins. Or even better: send a pull request to improve the plugin.

Relative paths across workspaces

Relative paths to import from other workspaces are not special-cased. For Knip,
it's better to be explicit and list other workspaces as dependencies in
package.json to be used in imports.

Solution: migrate from relative paths...

ts
import { something } from '../../common/file.ts';

...to dependency-based imports...

ts
import { something } from '@org/common';

An additional benefit is that Knip will report unused and unlisted dependencies
from now on.

Integrated monorepos

Multiple instances of configuration files like .eslintrc and
jest.config.json across the repository may be reported as unused when working
in a (mono)repo with a single package.json.

Solution: see integrated monorepos for more details and how to
configure plugins to target those configuration files.

Auto-mocking or auto-imports

Some frameworks have features like "auto-mocking" or "auto-imports" enabled,
such as Jest and Nuxt.

Solution: include such entry files by extending the entry file patterns.
This is recommended in most cases:

json
{
  "entry": ["src/index.ts", "src/models/*.ts"]
}

Alternatively, exceptions and outliers can be excluded from the analysis using
negated project patterns:

json
{
  "project": ["src/**/*.ts", "!**/__mocks__/**"]
}

Unresolved imports

Knip may ignore or be unable to resolve an import specifier or dependency
references. The most common causes for unresolved imports:

Template strings

Using template strings in dynamic imports might be ignored or not handled
properly by Knip, resulting in false positives. Examples of dynamic import
template strings:

ts
import(`./${value}.ts`);
import(`@org/name/dist/${value}.js`);

Solution: for internal source files, add the file(s) to the entry
patterns. For external dependencies, add the dependency to the
ignoreDependencies list.

Extensionless imports

Knip does not support extensionless imports for some non-standard extensions,
such as for .svg files. Bundlers like Webpack may support this, but Knip does
not. Here's an example:

ts title="app.vue"
import Component from './Component';    // → ✅ Should resolve to ./Component.vue
import ArrowIcon from '../icons/Arrow'; // → ❌ Does NOT resolve to ../icons/Arrow.svg

The first import is resolved properly, because .vue is a known extension if a
compiler is registered for that extension (e.g. via the Vue plugin). The second
import might not be resolved, because .svg is not a known extension.

Solution: include the extension. Hosts supporting ES Modules (e.g. Node.js,
browsers) require a file extension for import specifiers.

Unrecognized path aliases

Knip resolves config paths, aliases from enabled plugins that read framework
config (e.g. Vite/Webpack resolve.alias, SvelteKit $lib, Nuxt ~/@, Astro,
Docusaurus), and paths configured in knip.json.

Aliases go unrecognized only when no plugin covers the tool, the plugin is
disabled, or the alias lives somewhere Knip doesn't read.

Solution: configure paths or try relative imports. Otherwise, use
ignoreUnresolved as a last resort.

External aliased imports

External libraries may use virtual modules not declared in tsconfig.json,
knip.json or readable from a plugin's config.

For instance, unplugin-icons does this to import icons from icon sets as
components. Such imports are reported as unused. Use the paths configuration
option
to tell Knip where to find the icon types:

json title="knip.json"
{
  "paths": {
    "~icons/*": ["node_modules/unplugin-icons/types/[framework].d.ts"]
  }
}

Where [framework] is the name of the framework you're using (see available
types
).

Solution: configure paths.

Unused exports

By default, Knip does not report unused exports of entry files.

The most common causes for unused exports include:

Use the --exports flag to filter and focus only on issues related to
exports:

sh
knip --exports

Use includeEntryExports to report unused exports of entry files as well.
This can be set per workspace.

Namespace enumerations

For exports on an imported namespace, Knip considers all exports referenced if
that namespace is used in certain patterns like enumeration. Individual exports
are then not reported.

Solution: if all exports on imported namespaces should be considered
individually, include the nsExports issue type to disable the heuristic.

See namespace imports to see all related patterns.

External libraries

Are the exports consumed or imported by an external library, resulting in a
non-standard consumption of your exports? Here's an example:

```ts import loadable from '@loadable/component';
text
export const DynamicApple = dynamic(() =>
  import('./components.js').then(mod => mod.Apple)
);

export const LoadableOrange = loadable(() => import('./components.js'), {
  resolveComponent: components => components.Orange,
});
```
```ts export const Apple = () => 'Apple'; export const Orange = () => 'Orange'; ```

Knip understands Apple is used, since it's standard usage. But Orange is
referenced through a function of an external library. Knip includes external
type definitions to trace such usage, but in some cases it may not be able to
follow the reference through the library's API.

Custom elements

A custom element is registered under a tag name rather than imported by its
class name. Knip recognizes the registration and considers the class used. See
Custom Elements for all recognized patterns.

CommonJS member access

Named exports consumed via member access on a required variable (const mod = require('./mod'); mod.fn()) are not traced, so they may be reported as unused.
Destructuring (const { fn } = require('./mod')) is traced. See Working with
CommonJS
for the conventions that avoid these false positives.

Exclude exports from the report

To exclude false positives from the report, there are a few options:

  • Ignore exports used in file for exports used internally.

  • Individual exports can be tagged using JSDoc syntax.

  • Have the export in an entry file:

    • Add the file to the entry file patterns array in the configuration.
    • Move the export(s) to an entry file.
    • Add the file to the exports field of package.json
  • Re-export the unused export(s) from an entry file.

Missing unused exports?

Did you expect certain exports in the report, but are they missing? They might
be exported from an entry file. In that case, use --include-entry-exports
to make Knip also report unused exports in entry files.

The exports of non-standard extensions like .astro, .mdx, .vue or
.svelte are not available by default. See compilers for more details on
how to include them.

Types used only in signatures of used exports are not reported either.
TypeScript may need them in declaration files, which Knip cannot rule out for
the project or its consumers. Without such output, un-exporting them is safe.

Enum members

Unused enums and unused members of exported enums are reported by default.
Reporting such members can be disabled:

sh
knip --exclude enumMembers

Individual enum members can be tagged using JSDoc syntax.

Enums exported from entry files are ignored, and so are their members. Use
--include-entry-exports to make Knip also report members of unused exports
in entry files.

Namespace members

Unused members of exported TypeScript namespaces are reported by default.
Reporting such members can be disabled:

sh
knip --exclude namespaceMembers

Individual namespace members can be tagged using JSDoc syntax.

Namespaces exported from entry files are ignored, and so are their members. Use
--include-entry-exports to make Knip also report members of unused exports
in entry files.

Unused dependencies

Dependencies imported in unused files are reported as unused dependencies.
That's why it's strongly recommended to try and remedy unused files first.
Better entry and project file coverage will solve many cases of reported
unused dependencies.

The most common causes for unused dependencies include:

Use --dependencies to filter the report and focus only on issues related
to dependencies:

sh
knip --dependencies

:::caution[Monorepo]

In a monorepo, a dependency is unused in the root workspace's package.json if
it's also listed in a descendant workspace, and referenced only in the
descendant workspace.

:::

Missing or incomplete plugin

If a plugin exists and the dependency is referenced in the configuration file,
but its custom dependency finder does not detect it, then that's a false
positive. Please open a pull request or issue to fix it.

Solution: adding the configuration file as an entry file pattern may be a
temporary stopgap that fixes your situation, but it's better to create a new
plugin or fix an existing one.

Unrecognized reference

Sometimes a reference to a dependency is unrecognizable or unreachable to Knip,
so it's a false positive and incorrectly reported as unused.

Solution: add a new plugin or improve an existing one. If you don't feel
like a plugin could solve it, a last resort is to use ignoreDependencies.

If a binary (or "executable") is referenced you'll want to use ignoreBinaries
instead. See unlisted binaries.

Dependencies named after Node.js builtins

Some packages have the same name as a Node.js builtin (for instance buffer or
process).

Solution: if Knip reports such a dependency as unused, add it to
ignoreDependencies.

Conditional or dynamic dependencies

Dependencies added conditionally in configuration files may not be detected by
Knip. This happens because Knip loads and executes config files, and conditional
logic may evaluate differently during analysis.

For example, this Playwright configuration conditionally adds a reporter:

ts title="playwright.config.ts"
import { defineConfig } from '@playwright/test';

const reporters: any[] = [['list']];
if (process.env.REPORT_PORTAL_ENABLED) {
  reporters.push(['@reportportal/agent-js-playwright', config]);
}

export default defineConfig({
  reporter: reporters,
});

If process.env.REPORT_PORTAL_ENABLED evaluates to false when Knip runs, the
@reportportal/agent-js-playwright dependency won't be detected and may be
reported as unused.

This limitation exists because Knip executes configuration files to parse their
exported value. While Knip can parse configuration files statically using AST
(Abstract Syntax Tree) analysis, this approach becomes complex very quickly and
most of the time it is easier to use the ignoreDependencies
configuration option for conditionals.

json title="knip.json"
{
  "ignoreDependencies": ["@reportportal/agent-js-playwright"]
}

This pattern can be applied to any plugin that loads configuration files.

Type Definition Packages

Bundled types

Many packages come with their type definitions bundled. This means the
package.json#types field in the package points to an internal/local type
definition file. In this case, the separate types package is obsolete.

Knip reporting this is also useful for future regressions: if a package had a
DefinitelyTyped or similar package for its types before and later on starts
shipping those types bundled with the source code, Knip will report the obsolete
types dependency as unused.

Examples include ESLint, Webpack and React Router, rendering the
@types/eslint, @types/webpack and @types/react-router dependencies
obsolete respectively.

Solution: remove the types dependency (often @types/...)

Production types

In strict mode, a dependency used only in type-only source imports can
live in devDependencies. If the dependency is exposed through the package's
built declaration files, it is production usage and must instead be listed in
dependencies or required peerDependencies.

Knip finds this public type graph by following existing declaration files
exposed through main, types, typings, exports, and typesVersions. If
pnpm publishConfig overrides those manifest fields, Knip uses their published
values.

Private workspaces and packages using publishConfig.directory are not
analyzed. Knip only follows existing declaration output and does not guess from
source imports.

Unlisted dependencies

This means that a dependency is referenced directly in source code or
configuration, but not listed in package.json.

An unlisted dependency is usually a transitive dependency that's imported or
referenced directly. The dependency is installed (since it's a dependency of
another dependency) and lives in node_modules, but it's not listed explicitly
in package.json.

You should not rely on transitive dependencies for various reasons, including
control, security and stability.

Solution: install and list the dependency in dependencies or
devDependencies.

Unlisted binaries

Binaries are executable Node.js scripts. Some npm packages, when installed, add
one or more executable files to be used from scripts in package.json. Examples
include TypeScript that comes with the tsc binary, ESLint comes with eslint,
Next.js with next, and so on.

Knip detects such binaries in scripts and checks whether there's a package
installed that includes that binary. It looks up the bin field in the
package.json file of installed packages. If it doesn't find it, it will be
reported as an unlisted binary as there is no package that contains it.

Binaries that are installed on the OS already and thus likely not meant to be
installed from npm are not reported as unlisted (details: list of ignored
binaries in source
).

Missing binaries

An unused dependency and an unlisted binary with the same name indicates
node_modules not containing the relevant package. And this might be caused by
either the way your package manager installs dependencies and binaries, or by
not running Knip from the root of the repository.

Solution: run Knip from the project root. If needed, lint workspaces
individually
.

Sometimes binaries and how they're reported can be a bit confusing. See this
example:

json
{
  "name": "lib",
  "scripts": {
    "commitlint": "commitlint --edit"
  },
  "devDependencies": {
    "@commitlint/cli": "*"
  }
}

This example works fine without anything reported, as the @commitlint/cli
package includes the commitlint binary. However, some script may contain npx commitlint and here Knip assumes commitlint is the name of the package. This
technically works, as commitlint is a transitive dependency of
@commitlint/cli.

Solution: use npx @commitlint/cli

Feedback or false positives?

If you believe Knip incorrectly reports something as unused (i.e. there's a
false positive), feel free to create a minimal reproduction and open an
issue on GitHub. It'll make Knip better for everyone!


Packages Docs Src Content Docs Guides Issue Reproduction (`packages/docs/src/content/docs/guides/issue-reproduction.md`)


title: Issue Reproduction

If you encounter an issue or false positives when using Knip, you can open an
issue on GitHub
. This will help you in your project, and will also improve
Knip for everyone else!

Think of Knip as a kitchen sink, it handles a large amount of projects and
configurations, and your project is different from all others. Many factors may
influence the issue at hand, such as:

  • Code syntax, import and export structure in source files
  • Dependencies, scripts and entry files in package.json
  • TypeScript configuration in tsconfig.json
  • Enabled plugins and related configuration files
  • Dependent or depending workspaces in a monorepo
  • Knip configuration in knip.json

Keep it minimal

Create the minimum of source code and configuration with a few files to
reproduce and demonstrate the issue. Having this as a basis has many benefits:

  • Minimize barriers and unrelated contextual overhead
  • Optimize shared understanding of the situation
  • Serves as a "contract" to fix the actual issue
  • Files serve as a fixture to the project

Providing this with an issue description will help us help you and improve the
chances the issue can be looked into efficiently and in a timely manner.

Useful complements to a minimal reproduction might include:

  • Screenshots, videos, code snippets or log output
  • Links to an existing project repository
  • Applications scaffolded by tools like Angular or Svelte CLI

Before opening an issue

Before opening an issue, please make sure you:

  • are using the latest version
  • have read the relevant documentation
  • have searched existing issues
  • have checked the list of known issues

Please file only a single issue at a time, so each of them can be labeled and
tracked separately.

There is no need to open an issue if you're going to submit a pull request to
close it right away anyway. In that case it's preferred to keep things central
in either an issue or pull request.

Templates

A convenient way to create a minimal reproduction is by starting with one of
these templates in CodeSandbox or StackBlitz:

Template
Basic CodeSandbox StackBlitz
Monorepo CodeSandbox StackBlitz

Shoutout to CodeSandbox and StackBlitz for generously providing these
free dev containers!

Alternatives

Other solutions to share a minimal and reproducible case may work well too,
including:

  • A public repository on e.g. GitHub or GitLab.
  • A pull request with a new test and/or fixture folder.

The goal is to have an easy and common understanding and reproduction. If you're
unable to create a reproduction using one of the methods described then please
clearly explain this in the issue or contact me.

Pull Request

The optimal way is to add fixtures and failing tests to the Knip repository, and
open a pull request to discuss the issue! Also see instructions for
development
.


Packages Docs Src Content Docs Guides Namespace Imports (`packages/docs/src/content/docs/guides/namespace-imports.md`)


title: Namespace Imports

The intention of exports used through namespace imports may not always be clear
to Knip. Here's a guide to better understand how Knip handles such exports.

Example

We start off by having two exports:

ts title="my-namespace.js"
export const version = 'v5';
export const getRocket = () => '🚀';

The next snippet shows how to import all the exports above on a namespace. All
exports of the my-namespace.js module will be members on the NS object:

ts title="my-module.ts"
import * as NS from './my-namespace.js';
import send from 'stats';
send(NS);

The intention of export usage is not always clear. In the example above is
version or getRocket used? We're not sure, but we probably don't want them
to be reported as unused. The same goes for the next example:

ts title="my-module.ts"
import * as NS from './my-namespace.js';

export { NS };

If this is all usage of the NS namespace object, we also don't know whether
individual exports like version or getRocket will be used. However, if at
least one reference to a property such as NS.version is found, then the
individual exports are considered separately again and getRocket will be
marked as unused:

ts title="index.ts"
import { NS } from './my-module.js';

const version = NS.version;

The default heuristic

Knip uses the following heuristic to determine which of the individual exports
are used:

  • If there's one or more references to the import namespace object, but without
    any property access, all exports on that namespace are considered used.
  • Otherwise, exports are considered separately.

Below are a few more examples, and a way to disable this default behavior.

Examples

Let's take a look at more examples:

ts title="my-namespace.ts"
export const start = 1;

export const end = 1;

In the following cases all exports of my-namespace.ts are considered used:

ts title="index.ts"
import * as NS from './my-namespace.js';
import send from 'stats';

send(NS);

const spread = { ...NS };

const shorthand = { NS };

const assignment = NS;

const item = [NS];

type TypeOf = typeof NS;

Object.values(NS);

for (const fruit in NS) {
  //
}

export { NS };

export { NS as AliasedNS };

export = NS;

However, this is no longer the case when one of the properties is accessed:

ts title="index.js"
import * as NS from './namespace.js';

const begin = NS.start;

send(NS);

In this case, the end export will be reported as unused, even though the NS
object itself is referenced on its own as well.

Include `nsExports` and `nsTypes`

To disable the heuristic as explained above, and enforce Knip to consider each
export on a namespace individually, include the nsExports issue type:

json
{
  "include": ["nsExports"]
}

Or use the --include nsExports argument from the CLI. The nsTypes can be
added as well to do the same for exported types.

Including these adds them to the report; all other issue types are still
reported.


Packages Docs Src Content Docs Guides Performance (`packages/docs/src/content/docs/guides/performance.md`)


title: Performance

This page describes a few topics around Knip's performance, and how you might
improve it.

Knip does not want to tell you how to structure files or how to write your code,
but it might still be good to understand inefficient patterns for Knip.

Use the --debug and --performance flags to find potential bottlenecks.

Cache

Use --cache to speed up consecutive runs.

Ignoring files

Files matching the ignore patterns are not excluded from the analysis. They're
just not printed in the report. Use negated entry and project patterns to
exclude files from the analysis.

Read configuring project files for details and examples. Improving
configuration may have a significant impact on performance.

Metrics

Use the --performance flag to see how many times potentially expensive
functions (e.g. findReferences) are invoked and how much time is spent in
those functions. Example usage:

sh
knip --performance

ignoreExportsUsedInFile

The ignoreExportsUsedInFile option slows down the process slightly.
Typically, anywhere between 0.25% and 10% of total running time. To find out:

sh
knip --performance-fn hasRefsInFile

A last resort

In case Knip is unbearably slow (or even crashes), you could resort to lint
individual workspaces
.


Packages Docs Src Content Docs Guides Troubleshooting (`packages/docs/src/content/docs/guides/troubleshooting.md`)


title: Troubleshooting

We can distinguish two types of issues:

Also see the debug and trace options below that can help to
troubleshoot issues.

:::note[Rationale]

The JavaScript/TypeScript ecosystem has a vast amount of frameworks and tools.
Additionally, file locations, configuration semantics, command-line arguments
and so on vary wildly. Files and dependencies are referenced in many ways. Knip
tries harder than you think to cover it all.

Knip is intentionally strict to maximize its potential. It may initially report
many unused files. However, getting this right will result in great reports and
tidy codebases.

If it doesn't work out at first, a small change can go a long way.

:::

Lint issues reported by Knip

Knip reports lint issues in your codebase. See handling issues to deal with
the reported issues.

If you're considering filing an issue, please read issue reproduction
first.

Exit code 1 indicates a successful run, but lint issues were found.

Exceptions thrown by Knip

Knip may throw an exception, resulting in an unsuccessful run.

See known issues as it may be listed there and a workaround may be
available. If it isn't clear what's throwing the exception, try another run with
--debug to locate the cause of the issue with more details.

If you're considering filing an issue, please read issue reproduction
first.

Exit code 2 indicates an exception was thrown by Knip.

Debug

To better understand why Knip reports what it does, run it in debug mode by
adding --debug to the command:

sh
knip --debug

This will give a lengthy output, including:

  • Included workspaces
  • Used configuration per workspace
  • Enabled plugins per workspace
  • Glob patterns and options followed by matching file paths
  • Plugin config file paths and found dependencies per plugin
  • Compiled non-standard source files

Trace

Use --trace to see where exports or dependencies are used:

  • Use --trace-file [path] to output this only for the given file.
  • Use --trace-export [name] to output this only for the given export name.
  • Use --trace-dependency [name] to find where a dependency is imported
  • Use both to trace a specific named or default export of a certain file.

This works across re-exports, barrel files and workspaces. Here's an example
screenshot:

It's like a reversed module graph. Instead of traversing imports it goes in the
opposite direction and shows where exports are imported.

The --trace-dependency accepts strings for exact matches, but if it looks like
a regex that works too:

Use --workspace [filter] to filter accordingly.

Legend

Marker Description
✓ Referenced (not reported as unused)
✗ Reported as unused
⎆ Entry file

Each child node is prefixed with how the export is consumed: import,
importAs (shown as original → alias), importNS, or a re-export
(reExport, reExportAs, reExportNS, reExportStar). A refs: line lists
the references found in that file; a members: line shows per-member status for
namespaces and enums. An [ambiguous] line lists the competing definitions when
a name reaches the file through more than one export *. A name that reaches
the file only through export * is traced from its definition.

Opening an issue

If you want to open an issue, please see issue reproduction.

Understanding Knip

Looking to better understand how Knip works? The entry files and
plugins explanations cover two core concepts. After this you might want to
check out features like production mode and monorepos & workspaces.

In a more general sense, Why use Knip? explains what Knip can do for you.

Asking for help

If you can't find your answer in any of the aforementioned resources, feel free
to open an issue on GitHub.


Packages Docs Src Content Docs Guides Using Knip In Ci (`packages/docs/src/content/docs/guides/using-knip-in-ci.md`)


title: Using Knip in CI

Knip is your companion during local development. But it is even more valuable in
a continuous integration (CI) environment to prevent regressions over time. Knip
will notify you of unused dependencies, exports and files if you forgot to
remove them.

Knip will exit the process with code 1 if there are one or more issues.

GitHub Actions

Here's an example workflow configuration for GitHub Actions:

yaml
name: Lint project

on: push

jobs:
  lint:
    runs-on: ubuntu-latest
    name: Ubuntu/Node v24
    steps:
      - uses: actions/checkout@v6
      - uses: actions/setup-node@v6
        with:
          node-version: 24
      - name: Install dependencies
        run: npm install --ignore-scripts
      - name: Run knip
        run: npm run knip

Notes

  • Use --cache to speed up consecutive runs (default location:
    ./node_modules/.cache/knip).
  • See Reporters including the GitHub Actions reporter.
  • Consider running Knip twice: a default run and a production mode run.
  • See CLI arguments → Output for some relevant options such as
    --treat-config-hints-as-errors and --no-exit-code.
  • In CI environments, the --no-progress flag is set automatically.

Packages Docs Src Content Docs Guides Working With Commonjs (`packages/docs/src/content/docs/guides/working-with-commonjs.md`)


title: Working with CommonJS

CommonJS is the JavaScript module system using require() and module.exports
statements.

Knip works well with CommonJS. You don't need to use ES Modules or a
tsconfig.json to use Knip.

The dynamic nature of CommonJS leaves room for ambiguity: it's sometimes unclear
whether an export is a default or a named export (and thus how it should be
imported). So we'll have to agree on a few conventions to prevent false
positives. Those conventions are designed to minimize impact on existing
codebases, improve consistency, and ease migration to ES Modules or TypeScript.

For named exports, the recommendation is to assign keys to module.exports:

js
const B = function () {};

module.exports.A = { option: true };
module.exports.B = B;

Alternatively, assign an object with ONLY shorthand property assignments to
module.exports:

js
const A = function () {};
const B = { option: true };

module.exports = { A, B };

Anything else assigned to module.exports is considered a default export, and
should be imported as such.

The following default import of the named exports above will result in
all those exports reported as unused, even when referenced like below:

js
const DefaultImport = require('./common.js');
const runtime = [DefaultImport.A, DefaultImport.B];

Instead, do this:

js
const { A, B } = require('./common.js');
const runtime = [A, B];

Not recommended per se, but the following import syntax also results in the
named export A being used:

js
const runtime = [require('./common.js').A];

Add a non-shorthand property to turn the named object notation into a single
default export:

js
const A = function () {};
const B = { option: true };

module.exports = { __esModule: true, A, B };

The __esModule key could be named differently (but makes sense given it's an
informal "CJS/ESM interop" standard amongst compilers and bundlers).


Packages Docs Src Content Docs Overview Configuration (`packages/docs/src/content/docs/overview/configuration.md`)


title: Configuration

Defaults

Knip has good defaults and aims for "zero config". Here's a simplified version
of the default entry and project values:

json
{
  "entry": ["index.{js,ts}", "src/index.{js,ts}"],
  "project": ["**/*.{js,ts}"]
}

Entry files are the starting point for Knip to find more source files and
external dependencies.

Location

By default, Knip will look for a configuration file with the following names:

  • knip.json
  • knip.jsonc
  • .knip.json
  • .knip.jsonc
  • knip.ts
  • knip.js
  • knip.config.ts
  • knip.config.js
  • package.json (in the "knip" property)

If you want to use a custom file name or path, use the --config flag:

sh
knip --config path/to/knip.json

Customize

Your project structure may not match the default entry and project files.
Here's an example custom configuration to include .js files in the scripts
folder:

json title="knip.json"
{
  "$schema": "https://unpkg.com/knip@6/schema.json",
  "entry": ["src/index.ts", "scripts/{build,create}.js"],
  "project": ["src/**/*.ts", "scripts/**/*.js"]
}

The set of project files determines the analysis scope. Use negated project
patterns to exclude files from the analysis. See configuring project files
for details.

The values you set override the default values, they are not merged.

:::tip

Be specific with entry files. Minimize the number of entry files and wildcards
for better results.

Plugins are enabled automatically, and their entry files are added
automatically. Such as for Next.js, Astro, Remix, Vitest, Playwright and many
more.

:::

Knip looks for entry files in many places. Learn more in the next page about
entry files.

Configuration Options

See configuration file options.

Use JavaScript or TypeScript in a dynamic configuration file.

What's next?

The best way to understand Knip and what it can do for you is to read the pages
in the "Understanding Knip" sections, starting with entry files.

Want to learn more about some of the main features?

Having trouble configuring Knip?

Search this website using the bar at the top (Ctrl+K or ⌘+K).


Packages Docs Src Content Docs Overview Features (`packages/docs/src/content/docs/overview/features.md`)


title: Features

Overview of capabilities in support of the core feature: find many types of
issues
.

Also see related tooling.

Overview

Name Description or example
Auto-fix Use --fix to auto-fix issues
Cache Use --cache to speed up consecutive runs
Catalog Report & fix unused catalog entries
CommonJS CommonJS is still widely used & supported, but conditions apply
Compilers Support for Astro, MDX, Svelte, Vue and custom compilers
Configuration hints Display configuration hints to keep knip.json tidy
Debug Use --debug for troubleshooting
Filters Exclude or focus on specific issue types
Format Use --format with --fix to auto-format modified files
JSDoc tags Tag and exclude specific exports from the report
Memory usage Use --memory for detailed memory usage insights
Monorepos Workspaces are first-class citizen
Performance Use --performance for detailed timing insights
Plugins Over 100 plugins with custom entry paths and config parsing
Plugins: inputs Inputs are an affective mechanism to add entries, dependencies & more
Plugins: CLI arguments Tool-specific CLI argument parsing make plugins go the extra mile
Preprocessors Preprocess issues before being reported
Production mode Use --production to lint only production code
Reporters Choose from many built-in reporters or use your own
Rules Exclude or focus on specific issue types
Script parser Shell scripts and package.json contain entry paths and dependencies
Source mapping Map dist files back to src files
Strict mode Use --strict to tighten and lint only production dependencies
Trace Trace exports to find where they are used
Watch mode Use --watch for live updates of unused files and exports
Workspace Use --workspace to filter workspaces in a monorepo

Packages Docs Src Content Docs Overview First Cleanup (`packages/docs/src/content/docs/overview/first-cleanup.mdx`)


title: Your First Cleanup

import { Tabs, TabItem } from '@astrojs/starlight/components';

You installed Knip and ran it once. The first report can look like a lot. This
page helps you sort it out.

If the output is overwhelming, start with only a few issues:

```shell npm run knip -- --max-show-issues 5
text
# alternatively, include only a single type of issue:
npm run knip -- --include files
```
```shell pnpm knip --max-show-issues 5
text
# alternatively, include only a single type of issue:
pnpm knip --include files
```
```shell bun knip --max-show-issues 5
text
# alternatively, include only a single type of issue:
bun knip --include files
```
```shell yarn knip --max-show-issues 5
text
# alternatively, include only a single type of issue:
yarn knip --include files
```

:::tip

Address any configuration issues: they help you to help Knip!

:::

Read the report from the top

Knip groups issues by type, and unused files come first. Work the report
top-down, because findings come in chains: one unused file also makes its
exports and the dependencies it imports look unused. Fix the file and the rest
disappears. How Knip works explains why.

:::tip

Resist the urge to fix everything at once. Start at the top.

:::

Fix an unused file

Take the first unused file and decide which kind it is:

  • Genuinely dead: nothing references it and no dependency loads it. Delete it.
  • A reachability gap: some tool or convention loads it that Knip doesn't know
    about yet (a config file, a route, a generated entry). Don't delete it, but
    instead teach Knip: add the file to entry. Or rather, add the first file in
    its import chain.

Not sure which? Search the codebase for the filename. And for dynamic patterns
like import() or require() with a variable.

Re-run Knip after you delete a file. Exports and dependencies used only by that
chain of files are now gone from the report too.

Fix an unused export

An unused export is a value or type nothing else imports. Either remove only the
export keyword or delete the whole declaration if it has no use at all.

Knip can do this for you:

sh
knip --fix --exports

The export keyword is stripped from unused exports and types.

This might reveal more unused variables and types, resulting in more code to
delete. Then, Knip may find even more unused code. Rinse and repeat!

Fix a dependency

Two findings, two different fixes:

  • Unused dependency: in package.json, but nothing imports it. Remove it,
    unless a tool uses it that Knip has no plugin for (a reachability gap again).
  • Unlisted dependency: imported, but it's not in package.json. It resolves
    today only because something else happens to install it (a phantom or
    transitive dependency). Add it to package.json so it can't vanish from under
    you and to control the version being used.

When a result looks wrong

If Knip flags something you're sure is used, there might be a gap in what it can
reach. The fix is usually to teach Knip (add an entry, customize a plugin, add a
dependency), not to silence it with ignore.

Often the issue is subtle. There could be a typo, a missing file or room for
improvement in the configuration. Not filing it under ignore immediately will
pay off in the long run.

Resolving reported issues walks through every issue type. Configuring
project files
helps you tune and get the most out of Knip.

Keep it tidy

Commit your cleanup, then add Knip to CI so unused code can't pile up again. See
Running Knip in CI.

On a large or legacy codebase, adopt Knip gradually over chasing "inbox
zero" in one pass.

:::tip

Let your coding agent handle it for you: there are Knip Editor Extensions
and an MCP server using these docs to write the perfect knip.json for any
project.

:::


Packages Docs Src Content Docs Overview Screenshots Videos (`packages/docs/src/content/docs/overview/screenshots-videos.md`)


title: Screenshots & videos

Watch & auto-fix

This video demonstrates using the --watch and --fix options inside Visual
Studio Code:

This works in any terminal. See --watch and auto-fix for more details.

Trace

Here's an example screenshot that traces the mapIterator export in the
TypeScript codebase:

Inspect complicated import and re-export chains at a glance:

See Trace for more details.

Performance

An example screenshot showing --performance output for the Knip codebase:

Also see --performance.


Packages Docs Src Content Docs Reference Cli (`packages/docs/src/content/docs/reference/cli.md`)


title: CLI Arguments

General

`--help`

Shortcut: -h

Prints a summary of this page.

`--version`

Shortcut: -V

Print the version number.

`--no-progress`

Shortcut: -n

Don't show dynamic progress updates. Progress is automatically disabled in CI
environments.

`--config [file]`

Use an alternative path for the configuration file. Default locations:

  • knip.json
  • knip.jsonc
  • .knip.json
  • .knip.jsonc
  • knip.ts
  • knip.js
  • knip.config.ts
  • knip.config.js
  • package.json#knip

Shortcut: -c

`--use-tsconfig-files`

Per workspace, take project files from tsconfig.json (files + include,
minus exclude, node_modules and outDir) and from the tsconfig files in its
references instead of the project patterns. Workspaces without a
tsconfig.json fall back to their project patterns.

Implicitly enabled in the editor extension, MCP server and language server
when there is no Knip configuration file.

`--tsConfig [file]`

Shortcut: -t

Use an alternative path for the TypeScript configuration file.

Using -t jsconfig.json is also supported.

Default location: tsconfig.json

`knip-bun`

Run Knip using the Bun runtime (instead of Node.js + jiti).

shell
knip-bun

This is equal to bunx --bun knip

Requires Bun to be installed. Also see known issues for the type of
issues this might help with.

NO\_COLOR

The built-in reporters use the NO_COLOR friendly picocolors:

sh
NO_COLOR=1 knip

KNIP\_DISABLE\_RAW\_TRANSFER

Set KNIP_DISABLE_RAW_TRANSFER=1 to disable oxc-parser raw transfer and use the
normal parser path.

Mode

`--cache`

Enable caching.

Consecutive runs are 10-40% faster as the results of file analysis (AST
traversal) are cached. Conservative. Cache strategy based on file meta data
(modification time + file size).

See stale cache results for limitations and workarounds.

`--cache-location`

Provide alternative cache location.

Default location: ./node_modules/.cache/knip

`--include-entry-exports`

When a repository is self-contained or private, you may want to include entry
files when reporting unused exports:

sh
knip --include-entry-exports

Also see includeEntryExports.

`--no-gitignore`

Ignore .gitignore files.

`--production`

Shortcut: -p

Lint only production source files. This excludes:

  • entry files defined by plugins:
    • test files
    • configuration files
    • Storybook stories
  • devDependencies from package.json

Read more at Production Mode.

`--strict`

Shortcut: -s

Isolate workspaces and consider only direct dependencies. Implies production
mode
.

Read more at Production Mode.

`--watch`

Shortcut: -w

Watch current directory, and update reported issues when a file is modified,
added or deleted.

Watch mode focuses on imports and exports in source files. During watch mode,
changes in package.json or node_modules may not cause an updated report.

Scope

`--workspace [filter]`

Select one or multiple workspaces (including its ancestor, dependency and
dependent workspaces). The default behavior is to lint all configured
workspaces.

Shortcut: -W

See filter workspaces for more details and examples.

`--directory [dir]`

Shortcut: -D

Default: cwd (current directory)

Run the process from a different directory.

`--exclude`

Exclude provided issue types from report. Can be comma-separated or repeated.

Example:

sh
knip --exclude enumMembers

`--include`

Report only provided issue types. Can be comma-separated or repeated.

Example:

sh
knip --include files,dependencies
knip --include files --include dependencies

nsExports and nsTypes are off by default; including only those adds
them to the default report instead of narrowing it.

Available issue types when filtering output using --include or
--exclude:

  • files
  • dependencies
  • unlisted
  • binaries
  • unresolved
  • exports
  • nsExports
  • types
  • nsTypes
  • enumMembers
  • namespaceMembers
  • duplicates
  • catalog
  • catalogReferences

`--dependencies`

Shortcut to report only all types of dependency issues:

sh
--include dependencies,unlisted,binaries,unresolved,catalog,catalogReferences

`--exports`

Shortcut to report only all types of export issues:

sh
--include exports,nsExports,types,nsTypes,enumMembers,namespaceMembers,duplicates

`--files`

Shortcut to report only file issues:

sh
--include files

`--cycles`

Shortcut to report only circular dependencies:

sh
--include cycles

Also see the cycles reporter.

`--tags`

Exports can be tagged with known or arbitrary JSDoc/TSDoc tags:

ts
/**
 * Description of my exported value
 *
 * @type number
 * @internal Important matters
 * @lintignore
 */
export const myExport = 1;

And then include (+) or exclude (-) these tagged exports from the report
like so:

shell
knip --tags=-lintignore,-internal
knip --tags=+custom

This way, you can either focus on or ignore specific tagged exports with tags
you define yourself. This also works for individual enum and namespace members.

The default directive is + (include) and the @ prefix is ignored, so the
notation below is valid and will report only exports tagged @lintignore or
@internal:

shell
knip --tags @lintignore --tags @internal

Fix

`--fix`

Shortcut: -f

Read more at auto-fix.

`--fix-type`

Fix only issues of type, can be comma-separated or repeated.

More info about fixable types at issue types

`--allow-remove-files`

Allow Knip to remove files (with --fix).

`--format`

Shortcut: -F

Format modified files after --fix using the local formatter.

Output

`--preprocessor [preprocessor]`

Preprocess the results before providing it to the reporter(s).

Can be repeated. Examples:

sh
knip --preprocessor ./my-preprocessor.ts
sh
knip --preprocessor preprocessor-package

Preprocessors can also be configured in the Knip configuration file.

Also see Reporters & Preprocessors.

`--preprocessor-options [json]`

Pass extra options to the preprocessor as JSON string.

sh
knip --preprocessor ./preproc.ts --preprocessor-options '{"key":"value"}'

`--reporter [reporter]`

Available reporters:

  • symbols (default)
  • compact
  • codeowners
  • json
  • codeclimate
  • markdown
  • disclosure
  • github-actions
  • sarif

Can be repeated. Example:

sh
knip --reporter compact

Also see Reporters & Preprocessors.

`--reporter-options [json]`

Pass extra options to the reporter (as JSON string):

Example:

sh
knip --reporter codeowners --reporter-options '{"path":".github/CODEOWNERS"}'

`--no-config-hints`

Suppress configuration hints.

`--no-tag-hints`

Suppress tag hints.

`--treat-config-hints-as-errors`

Exit with non-zero exit code (1) if there are any configuration hints.

`--treat-tag-hints-as-errors`

Exit with non-zero exit code (1) if there are any tag hints.

`--max-issues`

Maximum number of issues before non-zero exit code. Default: 0

`--max-show-issues`

Maximum number of issues per type to display (does not affect exit code).

`--no-exit-code`

Exit with code zero (0) even when there are lint issues. Config load failures
and internal errors still exit with code 2.

The default exit codes:

Code Description
0 Knip ran successfully, no lint issues
1 Knip ran successfully, but there is at least one lint issue
2 Knip did not run successfully due to bad input, a plugin config load error or an internal error

Troubleshooting

`--debug`

Shortcut: -d

Show debug output.

`--memory`

txt frame=terminal
knip --memory

(results)

 # heapUsed  heapTotal  freemem
-- --------  ---------  -------
 0    42.09      70.91  2251.00
 1   927.04    1042.58  1166.47
 2   973.29    1047.33  1160.92
 3   971.54    1079.83  1121.66
 4   997.80    1080.33  1120.34
 5  1001.88    1098.08  1100.72
 6  1038.69    1116.58  1100.72
 7  1082.12    1166.33  1100.72
 8  1145.46    1224.50  1100.72
 9  1115.82    1240.25  1100.72
10  1182.35    1249.75   973.05
11   637.32    1029.17   943.63
12   674.30    1029.33   943.39
13   682.24    1029.33   941.63
14   707.70    1029.33   937.48

Total running time: 4.3s

`--memory-realtime`

Use this if Knip crashes to still see memory usage info over time:

txt frame=terminal
knip --memory-realtime

#  heapUsed  heapTotal  freemem
-  --------  ---------  -------
0     42.09      70.91  2251.00
1    927.04    1042.58  1166.47
...mem info keeps being logged...

(results)

`--performance`

Use this flag to get the count and execution time of potentially expensive
functions in a table. Example:

txt frame=terminal
$ knip --performance

(results)

Name                           size  min       max       median    sum
-----------------------------  ----  --------  --------  --------  --------
findReferences                  648     84.98   7698.61     96.41  70941.70
createProgram                     2   6295.84   7064.68   6680.26  13360.52
glob                              6      0.05    995.78    513.82   3150.87
findESLintDependencies            2      0.01     74.41     37.21     74.41
findGithubActionsDependencies     6      0.16     12.71      0.65     23.45
findBabelDependencies             2      0.00     38.75     19.37     38.75
...

Total running time: 5s
  • name: the internal Knip function name
  • size: number of function invocations
  • min: the fastest invocation
  • max: the slowest invocation
  • median: the median invocation
  • sum the accumulated time of all invocations

This is not yet available in Bun, since it does not support
performance.timerify (GitHub issue).

`--duration`

Shortcut: -u

Print total running time. Zero overhead — no timerify instrumentation, unlike
--performance.

`--performance-fn`

Limit the output of --performance to a single function to minimize the
overhead of the timerify Node.js built-in and focus on that function alone:

txt frame=terminal
$ knip --performance-fn resolveSync

(results)

Name         size   min   max   median   sum
-----------  -----  ----  ----  ------  ------
resolveSync  66176  0.00  5.69    0.00  204.85

Total running time: 12.9s

`--trace`

Trace exports to see where they are imported.

Also see Trace.

`--trace-dependency [name]`

Trace package or binary name to see where it's referenced. Implies
--trace.

`--trace-export [name]`

Trace export name to see where it's imported. Implies --trace.

`--trace-file [path]`

Trace file to see where its exports are imported. Implies --trace.


Packages Docs Src Content Docs Reference Configuration Hints (`packages/docs/src/content/docs/reference/configuration-hints.md`)


title: Configuration Hints

Knip emits configuration hints to help keep your configuration file tidy and
reduce drift.

They're warnings by default, not lint findings. But addressing the hints might
improve results significantly. The hints on this page are sorted by
impact/importance (most impactful first).

Use --treat-config-hints-as-errors or treatConfigHintsAsErrors to
make any configuration hint an error and result in a non-zero exit code to fail
CI.

Unconfigured projects

Too many unused files in a single workspace-codebase, caused by a missing
configuration file or incomplete configuration:

text
Create knip.json configuration file, and add entry and/or refine project files (42 unused files)

Or if you have a config file:

text
Add entry and/or refine project files (42 unused files)

Solution: Add and refine entry and/or project patterns.

Related resources:

Unconfigured workspaces

Too many unused files in a monorepo, caused by a missing configuration file or
incomplete configuration:

text
Create knip.json configuration file with workspaces["packages/app"] object (42 unused files)

Or if you have a config file:

text
Add entry and/or refine project files in workspaces["packages/app"] (42 unused files)

Solution: add a workspaces object to configuration. Add and refine entry
and/or project patterns. For instance, the example message translates to
configuration like this:

json
{
  "workspaces": {
    "packages/app": {
      "entry": ["src/App.tsx"],
      "project": ["src/**/*.ts"]
    }
  }
}

Related resources:

Top-level entry/project

In monorepos, Knip uses only entry and project under workspaces. The
top-level keys with the same name are ignored.

text
Remove, or move unused top-level entry to one of "workspaces"
Remove, or move unused top-level project to one of "workspaces"

Solution: Remove top-level entry and project keys, use these options in
workspaces instead.

jsonc
{
  "entry": ["src/App.tsx"], // move entry/project from here... ↴
  "project": ["src/**/*.ts"],
  "workspaces": {
    ".": {
      "entry": ["src/App.tsx"], // ...to the correct workspace(s) ↲
      "project": ["src/**/*.ts"],
    },
  },
}

Stale workspace configuration

A key in the workspaces configuration matches no workspace in the project, for
instance after the workspace was removed or renamed.

text
Remove from workspaces

Solution: Remove the stale key from workspaces, or correct its path.

Unused entry in ignore group

An entry of an ignore list is no longer needed, remove it.

text
Remove from ignoreWorkspaces
Remove from ignoreDependencies
Remove from ignoreBinaries
Remove from ignoreUnresolved

Solution: Remove the entry from the array.

Useless patterns

A glob pattern in entry or project does not match any files.

text
Refine entry pattern (no matches)
Refine project pattern (no matches)

Solution: Refine or remove the entry or project entry.

Redundant patterns

A glob/pattern is redundant, because it's already covered by defaults or added
by an enabled plugin.

text
Remove redundant entry pattern
Remove redundant project pattern

Solution: Remove the entry or project entry.

Missing package entry file

text
Package entry file not found

Solution: Refine or remove the entry in package.json.


Packages Docs Src Content Docs Reference Configuration (`packages/docs/src/content/docs/reference/configuration.md`)


title: Configuration

This page lists all configuration file options.

File Types

JSON Schema

A $schema field is a URL that you put at the top of your JSON file. This
allows you to get red squiggly lines inside of your IDE when you make a typo or
provide an otherwise invalid configuration option.

In JSON, use the provided JSON schema:

json title="knip.json"
{
  "$schema": "https://unpkg.com/knip@6/schema.json"
}

JSONC

In JSONC, use the provided JSONC schema:

json title="knip.jsonc"
{
  "$schema": "https://unpkg.com/knip@6/schema-jsonc.json"
}

Use JSONC if you want to use comments and/or trailing commas.

TypeScript

See dynamic configuration about dynamic and typed configuration files.

Project

`entry`

Array of glob patterns to find entry files. Prefix with ! for negation.
Example:

json title="knip.json"
{
  "entry": ["src/index.ts", "scripts/*.ts", "!scripts/except-this-one.ts"]
}

Also see configuration and entry files.

`project`

Array of glob patterns to find project files. Example:

json title="knip.json"
{
  "project": ["src/**/*.ts", "scripts/**/*.ts"]
}

Also see configuration and entry files.

`paths`

Tools like TypeScript, webpack and Babel support import aliases in various ways.
Knip automatically includes compilerOptions.paths from the TypeScript
configuration, but does not automatically use other types of import aliases.
They can be configured manually:

json title="knip.json"
{
  "paths": {
    "@lib": ["./lib/index.ts"],
    "@lib/*": ["./lib/*"]
  }
}

Each workspace can 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.

Workspaces

`workspaces`

Configure entry, project, plugins and most other options per workspace. Keys
are workspace directories (globs allowed); the root workspace is ".".

json title="knip.json"
{
  "workspaces": {
    ".": {
      "entry": ["scripts/*.ts"],
      "project": ["scripts/**/*.ts"]
    },
    "packages/*": {
      "entry": ["src/index.ts"],
      "project": ["src/**/*.ts"]
    }
  }
}

Workspaces configured here that aren't already defined in the root
package.json (or pnpm-workspace.yaml) are added to the analysis. Also see
Monorepos & Workspaces.

Individual workspace configurations may contain all other options listed on this
page, except for the following root-only options:

  • exclude / include
  • ignoreWorkspaces
  • workspaces

Workspaces can't be nested in a Knip configuration, but they can be nested in a
monorepo folder structure.

Also see Monorepos and workspaces.

Plugins

There are a few options to modify the behavior of a plugin:

  • Override a plugin's config or entry location
  • Force-enable a plugin by setting its value to true
  • Disable a plugin by setting its value to false
json title="knip.json"
{
  "mocha": {
    "config": "config/mocha.config.js",
    "entry": ["**/*.spec.js"]
  },
  "playwright": true,
  "webpack": false
}

It should be rarely necessary to override the entry patterns, since plugins
also read custom entry file patterns from the tooling configuration (see
Plugins → entry files).

Plugin configuration can be set on root and on a per-workspace level. If enabled
on root level, it can be disabled on workspace level by setting it to false
there, and vice versa.

Also see Plugins.

Rules & Filters

`rules`

See Rules & Filters.

`include`

See Rules & Filters.

`exclude`

See Rules & Filters.

`tags`

Exports can be tagged with known or arbitrary JSDoc/TSDoc tags:

ts
/**
 * Description of my exported value
 *
 * @type number
 * @internal Important matters
 * @lintignore
 */
export const myExport = 1;

And then include (+) or exclude (-) these tagged exports from the report
like so:

json
{
  "tags": ["-lintignore"]
}

This way, you can either focus on or ignore specific tagged exports with tags
you define yourself. This also works for individual enum and namespace members.

The default directive is + (include) and the @ prefix is ignored, so the
notation below is valid and will report only exports tagged @lintignore or
@internal:

json
{
  "tags": ["@lintignore", "@internal"]
}

:::caution

Tags must not contain hyphens or plus symbols, so it is recommended to stick to
letters and avoid snake-case.

:::

Also see JSDoc & TSDoc Tags.

`preprocessor`

Preprocess the results before providing them to the reporter(s). Can be a single
preprocessor or an array of preprocessors. Each value is a path to a local file
or an npm package name.

json title="knip.json"
{
  "preprocessor": ["./first.ts", "./second.ts"]
}

Also see Preprocessors.

`preprocessorOptions`

Extra options to pass to the preprocessor.

json title="knip.json"
{
  "preprocessor": "./my-preprocessor.ts",
  "preprocessorOptions": { "key": "value" }
}

`treatConfigHintsAsErrors`

Exit with non-zero code (1) if there are any configuration hints.

json title="knip.json"
{
  "treatConfigHintsAsErrors": true
}

`treatTagHintsAsErrors`

Exit with non-zero code (1) if there are any tag hints.

json title="knip.json"
{
  "treatTagHintsAsErrors": true
}

Ignore Issues

`ignore`

:::tip

Please read configuring project files before using the ignore option.

:::

Avoid ignore patterns. There is almost always a better solution:

  • Follow up on configuration hints (if there are any).
  • Fine-tune entry and project patterns.
  • Use production mode.
  • Other ignore* options.

NOTE: An exception to the rule: to temporarily report only issues in files
that match the negated ignore pattern:

json title="knip.json"
{
  "ignore": ["!src/dir/**"]
}

`ignoreFiles`

Array of glob patterns of files to exclude from the "Unused files" section only.

Unlike ignore, which suppresses all issue types for matching files,
ignoreFiles only affects the files issue type. Use this when a file should
remain analyzed for other issues (exports, dependencies, unresolved) but should
not be considered for unused file detection.

json title="knip.json"
{
  "ignoreFiles": ["src/generated/**", "fixtures/**"]
}

Suffix an item with ! to enable it only in production mode.

`ignoreBinaries`

Exclude binaries that are used but not provided by any dependency from the
report. Value is an array of binary names or regular expressions. Example:

json title="knip.json"
{
  "ignoreBinaries": ["zip", "docker-compose", "pm2-.+"]
}

Actual regular expressions can be used in dynamic configurations:

ts title="knip.ts"
export default {
  ignoreBinaries: [/^pm2-.+/],
};

Suffix an item with ! to enable it only in production mode.

`ignoreGlobalBinaries`

Defaults to true, suppressing missing-binary reports for common global commands such as git and docker. Set it to false to report these commands where a project dependency is expected:

json title="knip.json"
{
  "ignoreGlobalBinaries": false,
  "ignoreBinaries": ["git"]
}

Dependencies that provide a referenced binary still count as used, and ignoreBinaries still applies. Optional commands, including bare CI/hook commands and commands installed on demand, produce no missing-binary reports. Workspaces inherit the top-level value unless they override it.

`ignoreDependencies`

Array of package names to exclude from the report. Regular expressions allowed.
Example:

json title="knip.json"
{
  "ignoreDependencies": ["hidden-package", "@org/.+"]
}

Actual regular expressions can be used in dynamic configurations:

ts title="knip.ts"
export default {
  ignoreDependencies: [/@org\/.*/, /^lib-.+/],
};

Suffix an item with ! to enable it only in production mode.

Also see Unused dependencies.

`ignoreMembers`

Array of enum and namespace members to exclude from the report. Regular
expressions allowed. Example:

json title="knip.json"
{
  "ignoreMembers": ["render", "on.+"]
}

Actual regular expressions can be used in dynamic configurations.

`ignoreUnresolved`

Array of specifiers to exclude from the report. Regular expressions allowed.
Example:

json title="knip.json"
{
  "ignoreUnresolved": ["ignore-unresolved-import", "#virtual/.+"]
}

Actual regular expressions can be used in dynamic configurations:

ts title="knip.ts"
export default {
  ignoreUnresolved: [/^#virtual\/.+/],
};

`ignoreWorkspaces`

Array of workspaces to ignore, globs allowed. Example:

json title="knip.json"
{
  "ignoreWorkspaces": [
    "packages/go-server",
    "packages/flat/*",
    "packages/deep/**"
  ]
}

Suffix an item with ! to enable it only in production mode.

Prefix an item with ! to override an earlier wildcard.

`ignoreIssues`

Ignore specific issue types for specific file patterns. Keys are glob patterns
and values are arrays of issue types to ignore for matching files. This allows
ignoring specific issues (like unused exports) in generated files while still
reporting other issues in those same files.

json title="knip.json"
{
  "ignoreIssues": {
    "src/generated/**": ["exports", "types"],
    "**/*.generated.ts": ["exports", "enumMembers", "namespaceMembers"]
  }
}

`cycles`

Configure circular dependency (cycles) detection.

Set dynamicImports to include dynamic import() edges, which are excluded by
default (a dynamic import defers evaluation, so it does not cause the
initialization hazard cycles are meant to catch).

Use allow to accept specific cycles by exact path while keeping other cycles
issues enabled. Path entries are relative to the project root and omit the
closing repeat of the first file.

json title="knip.json"
{
  "cycles": {
    "dynamicImports": true,
    "allow": [["src/i18n/index.ts", "src/i18n/middleware.ts"]]
  }
}

Exports

`ignoreExportsUsedInFile`

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, when something is also no longer
used internally, it will be reported as unused.

json title="knip.json"
{
  "ignoreExportsUsedInFile": true
}

In a more fine-grained manner, to ignore only specific issue types:

json title="knip.json"
{
  "ignoreExportsUsedInFile": {
    "interface": true,
    "type": true
  }
}

Valid keys are the symbol types Knip distinguishes: class, enum, function,
interface, member, namespace, type and variable.

Set this option at root level to enable this globally, or within workspace
configurations individually.

`includeEntryExports`

By default, Knip does not report unused exports in entry files. When a
repository (or workspace) is self-contained or private, you may want to include
entry files when reporting unused exports:

json title="knip.json"
{
  "includeEntryExports": true
}

If enabled, Knip will report unused exports in entry source files. But not in
entry and configuration files as configured by plugins, such as next.config.js
or src/routes/+page.svelte. Entry files discovered only through package.json
scripts are also excluded; add them to entry to include their exports.

This will also enable reporting unused members of exported enums and namespaces.

Set this option at root level to enable this globally, or within workspace
configurations individually.

Compilers

Knip supports custom compilers to transform files before analysis.

:::note

Since compilers are functions, they can only be used in dynamic configuration
files (.js or .ts), not in JSON configuration files.

:::

`compilers`

Override built-in compilers or add custom compilers for additional file types.

Also see Compilers.


Packages Docs Src Content Docs Reference Dynamic Configuration (`packages/docs/src/content/docs/reference/dynamic-configuration.mdx`)


title: Dynamic Configuration

import { Tabs, TabItem } from '@astrojs/starlight/components';

TypeScript

Instead of a JSON file, you can use a JavaScript or TypeScript file for a
dynamic configuration and type annotations:

```ts title="knip.ts" import type { KnipConfig } from 'knip';
text
const config: KnipConfig = {
  entry: ['src/index.ts'],
  project: ['src/**/*.ts'],
};

export default config;
```
```js title="knip.js" /** @type {import('knip').KnipConfig} */ const config = { entry: ['src/index.ts'], project: ['src/**/*.ts'], };
text
export default config;
```

Function

You can export a regular or async function that returns the configuration object
as well:

```ts title="knip.ts" import type { KnipConfig } from 'knip';
text
const config: KnipConfig = async () => {
  const items = await fetchRepoInfo();

  return {
    entry: ['src/index.ts', ...items],
    project: ['src/**/*.ts'],
  };
};

export default config;
```
```ts title="knip.js" const config = async () => ({ entry: ['src/index.ts'], project: ['src/**/*.ts'], });
text
export default config;
```

defineConfig

Knip also exports a defineConfig helper. It returns the configuration
unchanged, so its only purpose is to type what you pass in:

ts title="knip.ts"
import { defineConfig } from 'knip/config';

export default defineConfig({
  entry: ['src/index.ts'],
  project: ['src/**/*.ts'],
});

The same works in a JavaScript configuration file, without the JSDoc annotation.
A function is accepted as well:

ts title="knip.ts"
import { defineConfig } from 'knip/config';

export default defineConfig(async () => ({
  entry: ['src/index.ts', ...(await fetchRepoInfo())],
  project: ['src/**/*.ts'],
}));

Packages Docs Src Content Docs Reference Faq (`packages/docs/src/content/docs/reference/faq.md`)


title: FAQ date: 2024-08-20

Introduction

Knip finds and fixes unused dependencies, exports and files. As a "kitchen sink"
in the npm ecosystem, it creates a comprehensive module graph of your project.

:::note[Rationale]

The JavaScript/TypeScript ecosystem has a vast amount of frameworks and tools.
Additionally, file locations, configuration semantics, command-line arguments
and so on vary wildly. Files and dependencies are referenced in many ways. Knip
tries harder than you think to cover it all.

:::

This FAQ explains a few design decisions and why certain things work the way
they do, here and there in more depth than the rest of the docs.

:::tip

Try the Knip MCP Server or the Knip Editor Extension. Let your coding
agent use the built-in MCP Server and create a custom knip.json for you, so
you don't have to.

:::

Common Pitfalls

Why shouldn't I ignore or disable configuration hints?

Configuration hints are critical for building a healthy and accurate module
graph. They usually indicate that Knip cannot resolve a dependency, plugin, or
entry file. If you ignore or disable these hints, Knip's understanding of your
project will be incomplete, which inevitably leads to false positives (reporting
used code as unused). Always address configuration hints first before looking at
other reported issues.

Why is it a bad idea to use `ignore` patterns like I do in ESLint?

Knip is not a regular file-based linter like ESLint. It works by analyzing the
entire interconnected module graph of your project. Using ignore patterns does
not exclude files from the analysis, it only suppresses the reporting of issues
in those files. This hides real issues and creates blind spots. Instead of
ignoring files, ensure your entry points and plugins are configured correctly,
and use project patterns to define the boundaries of your codebase. Read more:
Configuring Project Files.

If you have specific exports (such as types) that are only used within the file
they are defined, use the ignoreExportsUsedInFile configuration option
rather than ignoring the file entirely.

How should I exclude tests and development tools from the analysis?

A common mistake is trying to exclude test files, storybooks, or development
tools using project or ignore patterns. The correct approach is to use
production mode. This mode is specifically designed to strictly analyze
only your production source code and dependencies, automatically excluding
tests and devDependencies without requiring complex ignore rules.

Why shouldn't I run `knip --fix` immediately?

Running knip --fix before your configuration is fully settled is dangerous. If
your configuration is missing entry points or has unresolved hints, Knip might
think perfectly valid, actively used code is unused. Auto-fixing in this state
can lead to deleting code that your application relies on. Always verify the
reported issues manually and ensure your configuration is solid before using the
--fix flag.

Comparison

Why isn't Knip an ESLint plugin?

Linters like ESLint analyze files separately, while Knip lints projects as a
whole.

Knip requires a full module and dependency graph to find clutter across the
project. Building the graph is substantial work, especially in monorepos.

File-oriented linters like ESLint are complementary to Knip.

Isn't tree-shaking enough?

No. They share a goal: improving UX by removing unused code. But tree-shaking
and Knip are different, complementary tools.

Tree-shaking is a build-time optimization to reduce production bundle size. It
operates on bundled production code, which might include external code, and runs
automatically (out of your hands).

On the other hand, Knip is a project linter for the development and QA phase. It
reports and fixes only your own source code, and leaves the changes for you to
review. It focuses on inter-file dependencies, so dead code within a single file
may slip through.

Besides those differences, Knip has a broader scope:

For how Knip compares to other unused-code tools and how to migrate from them,
see Comparison & Migration.

Synergy

Why does Knip have plugins?

Plugins are an essential part of Knip. They save you a lot of configuration out
of the box, by adding entry files as accurately as possible and only for the
tools actually installed. More powerful still are their custom parsers for
configuration files and command-line argument definitions.

For instance, Vitest has the environment configuration option. The Vitest
plugin knows "node" is the default value for environment which does not
require an extra package, but will translate "edge-runtime" to the
@edge-runtime/vm package. This allows Knip to report it if this package is not
listed in package.json, or when it is no longer used after changes in the
Vitest configuration.

Configuration files may also contain references to entry files. For instance,
Jest has setupFilesAfterEnv: "<rootDir>/jest.setup.js" or a reference may
point to a file in another workspace in the same monorepo, e.g. setupFiles: ['@org/shared/jest-setup.ts']. Those entry files may also contain imports of
internal modules or external dependencies, and so on.

Why is Knip so heavily engineered?

Even though a modular approach has its merits, for Knip it makes sense to have
all the pieces in a single tool.

Building up the module and dependency graph takes more than standard module
resolution. It needs dynamic analysis, not just static: plugin parsers actually
load and execute dynamic tooling configuration files to read their exported
value. On top of that, exports consumed by external libraries require type
information. And shell script parsing is needed to find the right entry files,
configuration files and dependencies accurately.

The rippling effect of plugins recursively pulling in more entry files and
dependencies is exactly what "comprehensive" refers to.

Design decisions

Why isn't production mode the default?

The default mode of Knip includes all source files, tests, dependencies, dev
dependencies and tooling configuration.

On the other hand, production mode considers only source files and production
dependencies. Plugins add only production entry files.

Which mode should've been the default? They both have their merits:

  • Production mode catches dead production code and dependencies. This mode has
    the most impact on UX, since less code tends to be faster and safer.
  • Default mode potentially catches more issues, e.g. lots of unused plugins of
    tooling, including most issues found in production mode. This mode has the
    most impact on DX, for the same reason.

Also see production mode.

Why doesn't Knip just read the lockfile?

Knip reads the package.json file of each dependency. Most of the information
required is in the lockfile as well, which would be more efficient. However,
lockfiles lack some data, including:

  • It requires lockfile parsing for each lockfile format and version of each
    package manager.
  • The lockfile doesn't contain whether the package has types included.
  • The lockfile doesn't contain entry point fields (main, module, exports)
    needed to resolve what a dependency actually exposes.
  • The lockfile doesn't contain bin entries to determine installed binaries.

Why doesn't Knip use an existing module resolver?

It does: Knip uses oxc-resolver. A runtime or bundler resolver can't be
used as-is, because Knip resolves imports to your source across every workspace
in a (mono)repo. It has to:

  • Resolve to source (src/module.ts), not type definitions (module.d.ts) or
    the build artifact a package.json#main like dist/module.js points to
  • Resolve non-standard extensions like .svelte, .astro and .css so those
    files enter the module graph

oxc-resolver is configurable enough for this. Combined with a thin layer for
Knip-configured paths and plugin aliases. This covers resolution across all
workspaces andscript parsing and references to files in other workspaces.

Why doesn't Knip match my TypeScript project structure?

Repositories and workspaces in a monorepo aren't necessarily structured like
TypeScript projects. Put simply, the location of package.json files isn't
always adjacent to tsconfig.json files. Knip follows the structure of
workspaces in a monorepo.

An additional layering of TypeScript projects would complicate things. The
downside is that a tsconfig.json file not used by Knip may have conflicting
module resolution settings, potentially resulting in missed files.

In practice, this is rarely an issue. Knip sticks to the workspaces structure
and installs a single "kitchen sink" module resolver function per workspace.
Different strategies might add more complexity and performance penalties, while
the current strategy is simple, fast and good enough.

Note that any directory with a package.json not listed in the root
package.json#workspaces can be added to the Knip configuration manually to
have it handled as a separate workspace.

If you do want Knip to take its project files from tsconfig.json rather than
its own project patterns, see --use-tsconfig-files.

Why doesn't Knip just use `ts.findReferences`?

TypeScript has a very good "Find references" feature, that you might be using in
your IDE as well. There are a few reasons Knip doesn't use it:

  • It requires the full TypeScript language service, which is heavy to
    initialize.
  • It must be called per symbol. A project with thousands of exports would need
    thousands of calls, each scanning potentially all files. Knip parses each file
    once and resolves all export usages from the resulting graph.
  • It operates within a single TypeScript program. Monorepos with multiple
    tsconfig.json files would need separate language service instances.
  • It cannot see into non-standard files like .vue, .svelte and .astro.

Knip's module graph is also serializable and cacheable, and usable for other
tools to build upon.

Why doesn't Knip have...?

Examples of features that have been requested include:

  • Expose programmatic API
  • Add local/custom plugins
  • Expose the module and dependency graph
  • Custom AST visitors, e.g. to find and return:
    • Unused interface/type members
    • Unused object members (and e.g. React component props)
    • Unused object props in function return values
  • Analyze workspaces in parallel
  • Support Deno

These are all interesting ideas, but most increase the API surface area, and all
require more development efforts and maintenance. Time is limited and
sponsorships currently don't cover - this can change though!

Under the hood

Where does Knip look for entry files?

Knip discovers them from defaults, package.json, enabled plugins, source-code
imports and resolution calls (require.resolve, import.meta.resolve, new URL, new Worker, module.register), and parsed scripts. See Entry
Files
for the complete list. Entry files are added to the module graph, and
module resolution may result in more entry files recursively until none
remain.

What does Knip look for in source files?

Knip uses oxc-parser to parse source files, it's powerful and fault-tolerant.
Knip visits all nodes of the generated AST to find:

  • Imports and dynamic imports of internal modules and external dependencies
  • Exports
  • Accessed properties on namespace imports and re-exports to track individual
    export usage
  • Entry files from require.resolve, import.meta.resolve, new URL(specifier, import.meta.url), new Worker(…), child_process fork/spawn/execFile,
    and module.register()
  • Scripts passed to the script parser: tagged $ templates (bun,
    execa, zx) and node:child_process, execa and nano-spawn calls

What's in the graph?

Once built, the graph holds everything needed to produce the report: every
issue type, from unused files and dependencies to unused exports, types and
enum members.

The graph also surfaces more interesting details, such as:

  • Circular references
  • Usage numbers per export
  • Export usage across workspaces in a monorepo
  • List of all binaries used
  • List of all used (OS) binaries not installed in node_modules

How does Knip handle non-standard import syntax?

Knip tries to be resilient against import syntax like what's used by e.g.
webpack loaders or Vite asset imports. Knip strips off the prefixes and suffixes
in import specifiers like this:

ts title="component.ts"
import Icon from './icon.svg?raw';
import Styles from '-!style-loader!css-loader?modules!./styles.css';

In this example, the style-loader and css-loader dependencies should be
dependencies found in webpack configuration, handled by Knip's webpack plugin.

TypeScript

What's the difference between workspaces, projects and programs?

A workspace is a directory with a package.json file. They're configured in
package.json#workspaces (or pnpm-workspaces.yml). In case a directory has a
package.json file, but is not a workspace (from a package manager
perspective), it can be added as a workspace to the Knip configuration.

Projects - in the context of TypeScript - are directories with a tsconfig.json
file. They're not a concept in Knip.

Knip analyzes all workspaces using a single module graph with a shared module
resolver.

What's up with that configurable `tsconfig.json` location?

Two settings point Knip at a tsconfig.json, and they do different things:

  • --tsConfig [file] (CLI, root only) sets an alternative location for
    the root tsconfig.json. Knip reads its compilerOptions, notably paths
    and moduleResolution.
  • typescript.config (plugin, per workspace) sets the location the
    TypeScript plugin reads to extract referenced dependencies from extends,
    compilerOptions.types and JSX settings:
json title="tsconfig.json"
{
  "extends": "@tsconfig/node20/tsconfig.json",
  "compilerOptions": {
    "jsxImportSource": "hastscript/svg"
  }
}

From this example, Knip can tell whether @tsconfig/node20 and hastscript are
listed in package.json.

Note that the TypeScript plugin doesn't add TypeScript support to Knip (that's
built-in); like other plugins, it extracts dependencies from tsconfig.json. If
path aliases from compilerOptions.paths aren't picked up, either point
--tsConfig at the right tsconfig.json or add paths to the Knip config
(per workspace).

What does `--use-tsconfig-files` do?

By default Knip discovers project files with its own project patterns. The
--use-tsconfig-files flag instead takes them from tsconfig.json per
workspace. Those become TypeScript program files, so Knip analyzes their exports
and dependencies but does not report them as unused files: delegating project
definition to tsconfig.json declares them intentional, including empty files.
This is by design: use the default project patterns (discovery mode) to flag
dangling files. Implicitly enabled in the editor extension, MCP server and
language server
without a Knip configuration file.

Compilers

How does Knip handle Svelte or Astro files?

To further increase the coverage of the module graph, non-standard files other
than JavaScript and TypeScript modules should be included as well. For instance,
.mdx and .astro files can import each other, internal modules and external
dependencies.

Knip includes basic "compilers" for a few common file types (Astro, MDX, Svelte,
Vue). These are lightweight regex-based extractors, not actual compilers. You
can override the built-in compilers with your project's actual compiler, and add
additional ones for other file types.

Why are the exports of my `.vue` files not used?

The built-in compilers extract import statements only, so the exports declared
in a .vue or .svelte file aren't analyzed and may be reported as unused.
Override the built-in with a real compiler. See Compilers.


Packages Docs Src Content Docs Reference Integrations (`packages/docs/src/content/docs/reference/integrations.md`)


title: Integrations

VS Code Extension

The official VS Code extension provides a rich integration with the Knip
Language Server
:

  • Diagnostics: Inline warnings for unused dependencies, exports and files
  • Hover Information: Hover over exports to see import and usage locations
  • Imports Tree View: Direct links to implementations
  • Exports Tree View: Direct links to import and usage locations
  • Contention Detection: Warnings for circular dependencies, ambiguous,
    shadowed and diamond-shaped import chains
  • Built-in MCP Server: Automated configuration support for coding agents

Find Knip on the VS Code Marketplace and find Knip in the Open VSX
Registry
.

See below for screenshots.

JetBrains Plugin

A community plugin is available for JetBrains IDEs including WebStorm, IntelliJ
IDEA, and others. The plugin is powered by the Knip Language Server and
provides diagnostics. Find Knip on the JetBrains Marketplace.

MCP Server

The standalone MCP Server enables coding agents to configure Knip automatically.
Tell your agent to "configure knip" and it will use the available tools to
create, analyze and optimize your knip.json configuration.

The Knip MCP Server is available separately and built into the Knip VS
Code Extension
.

Start:

sh
npx @knip/mcp

Note: The VS Code extension has this MCP Server built-in.

Language Server

The IDE integrations are powered by the Knip Language Server. It builds the full
module graph of your project and provides a session with a graph explorer. See
the Knip Language Server documentation for more details and information on
integrating Knip.

VS Code Extension Screenshots

Lint Findings

Lint Findings

Imports & Exports

Hover over imports and exports

Contention

The extension shows circular dependencies and three kinds of export contention:
ambiguous, shadowed, and converged.

Circular Dependencies

Circular Dependencies

Ambiguous

Different bindings compete for the same name, with no winner. Knip lists the
competing bindings:

Ambiguous

Shadowed

An explicit export hides bindings from export *. Knip labels the site as
"shadowing" and lists the winning and hidden bindings:

Shadowed

Converged

The same binding arrives through multiple re-export paths. Knip shows where
those paths meet and the binding they share.

Converged

Settings

VS Code Extension Settings


Packages Docs Src Content Docs Reference Issue Types (`packages/docs/src/content/docs/reference/issue-types.md`)


title: Issue Types tableOfContents: false

Knip reports the following types of issues:

Title Description Key
Unused files Unable to find a reference to this file 🔧 files
Unused dependencies Unable to find a reference to this dependency 🔧 dependencies
Unused devDependencies Unable to find a reference to this devDependency 🔧 dependencies²
Referenced optional peerDependencies Optional peer dependency is referenced dependencies²
Unlisted dependencies Used dependencies not listed in package.json unlisted
Unlisted binaries Binaries from dependencies not listed in package.json binaries
Unused catalog entries Unable to find a reference to this catalog entry 🔧 catalog
Unresolved catalog references Catalog does not contain the referenced package catalogReferences
Unresolved imports Unable to resolve this (import) specifier unresolved
Unused exports Unable to find a reference to this export 🔧 exports
Unused exported types Unable to find a reference to this exported type 🔧 types
Exports in used namespace Namespace with export is referenced, but not export itself 🔧 🟠 nsExports
Exported types in used namespace Namespace with type is referenced, but not type itself 🔧 🟠 nsTypes
Unused exported enum members Unable to find a reference to this enum member 🔧 enumMembers
Unused exported namespace members Unable to find a reference to this namespace member 🔧 namespaceMembers
Duplicate exports This is exported more than once duplicates
Circular dependencies These files (in)directly import each other at runtime 🟠 cycles

Legend

Description
🔧 Auto-fixable issue types
🟠 Not included by default (include with filters)

Notes

  1. When an issue type has zero issues, it is not shown.
  2. Including or excluding dependencies (via CLI or configuration)
    automatically includes or excludes devDependencies and
    optionalPeerDependencies. In rules, each key can be set individually.
  3. In strict production mode, devDependencies are not included.
  4. The types issue type includes enum, interface and type exports.
  5. cycles defaults to the warn rule (reported, but not counted as an
    error). Set rules.cycles to error to fail on circular dependencies.

Packages Docs Src Content Docs Reference Jsdoc Tsdoc Tags (`packages/docs/src/content/docs/reference/jsdoc-tsdoc-tags.md`)


title: JSDoc & TSDoc Tags

JSDoc or TSDoc tags can be used to make exceptions for unused or duplicate
exports.

Knip tries to minimize configuration and introduces no new syntax. That's why it
hooks into JSDoc and TSDoc tags.

:::caution

Adding tags or excluding a certain type of issues from the report is usually not
recommended. It hides issues, which is often a sign of code smell or ambiguity
and ends up harder to maintain. It's usually better to refactor the code (or
report an issue with Knip for false positives).

:::

Tags also work in regular block comments (/* ... */) and standalone line
comments (// ...) before a declaration. These comments keep suppression tags
out of JSDoc hover information.

Tags in consecutive comments are combined. Trailing // comments are ignored.

Tags

Use arbitrary tags to exclude or include tagged exports from the report.
Example:

ts
/** @lintignore */
export const myUnusedExport = 1;

// @lintignore
import Unresolved from './generated/lib.js';

And then include (+) or exclude (-) these tagged exports from the report
like so:

shell
knip --tags=-lintignore,-internal

Tags can also be configured in knip.json.

When an excluded tag is no longer necessary because the export is actually used,
Knip reports a tag hint:

sh
Tag hints (1)
ignored  unimported.ts  Unused tag in unimported.ts: ignored → @custom

This helps you clean up tags that were added to suppress issues that have since
been resolved.

`@public`

By default, Knip reports unused exports in non-entry files.

Tag the export as @public and Knip will not report it.

Example:

ts
/**
 * @public
 */
export const unusedFunction = () => {};

This tag can also be used to make exceptions in entry files when using
--include-entry-exports.

JSDoc: @public and TSDoc: @public

`@internal`

Internal exports are not meant for public consumption, but only for internal
usage such as tests. This means they would be reported in production mode.

Mark the export with @internal and Knip will not report the export in
production mode.

Example:

ts
/** @internal */
export const internalTestedFunction = () => {};

In general it's not recommended to expose and test implementation details, but
exceptions are possible. Those should not be reported as false positives, so
when using production mode you'll need to help Knip out by tagging them as
@internal.

TSDoc: @internal

`@alias`

Knip reports duplicate exports. To prevent this, tag one of the exports as
@alias.

Example:

ts
export const Component = () => {};

/** @alias */
export default Component;

An alternative solution is to use --exclude duplicates and exclude all
duplicates from being reported.

JSDoc: @alias

`@beta`

Works identical to @public. Knip ignores other tags like @alpha and
@experimental.

TSDoc: @beta


Packages Docs Src Content Docs Reference Known Issues (`packages/docs/src/content/docs/reference/known-issues.md`)


title: Known Issues

Known limitations and workarounds for errors during a Knip run.

See handling issues to learn more about dealing with lint issues.

Exceptions from config files

Knip plugins may fail to load a JavaScript or TypeScript configuration file such
as webpack.config.js or vite.config.ts:

sh
$ knip
ERROR: Error loading vite.config.ts

Knip exits with code 2 when a plugin config file fails to load.
--no-exit-code does not suppress this error.

Knip may load such files differently, in a different environment, with missing
environment variables, missing path aliases, etcetera. Use --debug to locate
the cause of the issue with more details.

Potential workarounds:

  • For "Cannot find module" and "ENOENT: no such file/dir, open './file'" errors:
    • Install dependencies
    • Use absolute paths, e.g. with path.join() or path.resolve()
    • Set path aliases
  • Set missing environment variable(s), potential solutions:
    • Use a helper package like dotenvx
    • KEY=VAL knip
    • node --env-file .env $(which knip)
  • Run the build script to generate required files.
  • Disable loading the file by overriding the default config for that plugin.
    • Example: vite: { config: [] }
    • In a monorepo, be more specific like so: workspaces: { "packages/lib": { vite: { config: [] } } }
    • If this helps, add the file as an entry file for static analysis.
  • Disable the related plugin.
    • Example: eslint: false
    • In a monorepo, be more specific like so: workspaces: { "packages/lib": { eslint: false } }
    • If this helps, add the file as an entry file for static analysis.
  • As a last resort, ignore the workspace: ignoreWorkspaces: ["packages/lib"].

Path aliases in config files

Loading the configuration file (e.g. cypress.config.ts) for one of Knip's
plugins may give an error:

sh
$ knip
Analyzing workspace ....
Error loading .../cypress.config.ts
Reason: Cannot find module '@alias/name'
Require stack:
- .../cypress.config.ts

Some tools (such as Cypress and Jest) support using TypeScript path aliases in
the configuration file.

Potential workarounds:

  • Rewrite the import in the configuration file to a relative import.
  • Inject support with a module like tsx: NODE_OPTIONS="--import tsx" knip
  • Or tsconfig-paths: NODE_OPTIONS="--import tsconfig-paths/register.js" knip
  • Use Bun with knip-bun.
  • See exceptions from config files for more potential workarounds.

Nx Daemon

In Nx projects you might encounter this error:

sh
NX   Daemon process terminated and closed the connection

The solution is to disable the Nx Daemon:

sh
NX_DAEMON=false knip

Raw transfer memory errors

On machines with low RAM or Windows with Node.js >=22, oxc-parser raw transfer
may fail under memory pressure:

sh
RangeError: Array buffer allocation failed

Disable raw transfer to use the normal parser path:

sh
KNIP_DISABLE_RAW_TRANSFER=1 knip

Stale cache results

With --cache, configuration or path alias changes may leave results stale. An
import may still be reported as unresolved after the missing file is added.
Newly added .gitignore files are also not detected automatically.

Run without --cache for fresh results, or delete the cache directory
(node_modules/.cache/knip by default, configurable with --cache-location)
before running with --cache again.


Packages Docs Src Content Docs Reference Plugins (`packages/docs/src/content/docs/reference/plugins.md`)


title: Plugins (177) tableOfContents: false

:::section{.columns.min200}

:::



This is an overview of related tooling for features Knip does not support.

Unused imports & variables

Knip doesn't look for unused imports and variables within a file. The focus is
on exported values and types across files.

Use ESLint, Biome or oxlint to find unused variables within
files.

Use remove-unused-vars to remove unused code within files, but in a more
valiant way. Using input from any of the above linters, it actually removes a
lot more unused code. This pairs great with Knip.

Unused properties

Knip does not yet support finding unused members of types, interfaces and
objects. This includes returned objects from exported functions and objects
passed as React component props.

Knip does support finding unused members of enums and classes, and exported
values and types on imported namespaces.

Circular dependencies

Knip reports circular dependencies as an opt-in cycles issue type. The
editor integrations show cyclic paths directly in IDE.

Other dedicated tools include DPDM, Madge and skott. Linters such
as ESLint have similar plugins and rules, e.g. the eslint-plugin-import-x
plugin has the import-x/no-cycle and oxlint has the import/no-cycle
rule.

Cleanup

The e18e.dev website and in particular the Cleanup section is a
great resource when dealing with technical debt.


Packages Docs Src Content Docs Typescript Unused Dependencies (`packages/docs/src/content/docs/typescript/unused-dependencies.md`)


title: Unused dependencies prev: false next: false

One of Knip's core features is finding unused dependencies in your JavaScript
and TypeScript projects. And it comes with many more features to remove clutter
and keep your projects in great shape.

Why are unused dependencies a problem?

Having unused dependencies in your package.json is an issue for various
reasons:

  • They might end up in the final production bundle, increasing size and load
    times for end users.
  • They waste space in node_modules and add to the installation time of the
    project.
  • They slow down tooling such as linters and bundlers that analyze dependencies.
  • They are confusing and noisy in package.json.
  • They cause unnecessary extra work when managing and upgrading dependencies.
  • They can cause version conflicts with other dependencies in use.
  • They can cause false security alerts.
  • They might have restrictive licenses and make your project subject to theirs.
  • They usually come with transitive dependencies that have the same issues.

How do I find unused dependencies?

Use Knip to find and remove unused dependencies. It also finds dependencies that
are missing in package.json and has a lot more features to keep your
JavaScript and TypeScript projects tidy.

It's easy to get started and make package management easier and more fun!

How does Knip identify unused dependencies?

Knip works by analyzing package.json files, source code and configuration
files for other tooling in the project to find unused and missing dependencies.
Knip has many heuristics, plugins and compilers to fully automate the
process.

Can Knip remove unused dependencies?

Yes, Knip can automatically remove unused dependencies installed by a package
manager like npm or pnpm for you. Add the --fix argument to auto-fix and
remove unused dependencies from package.json.

Can Knip detect missing dependencies?

Yes, Knip detects missing dependencies. It analyzes package.json files, and
reports packages that are missing. They should be added to package.json to
avoid relying on transitive dependencies that can cause version mismatches and
breakage.

Does Knip work with monorepos?

Yes, Knip has first-class support for monorepos and workspaces. It analyzes
all workspaces in the project and understands their relationship.

For instance, if a dependency is listed in the root package.json it does not
need to be listed in other workspaces. Except if you enable --strict checking.

Does Knip separate dependencies and devDependencies?

Yes, Knip understands the difference between dependencies and devDependencies.
It has a production mode to focus on production code only and find dead
code and dependencies that would otherwise only be referenced by tests and other
tooling. This allows you to remove both unused exported code and their tests.

Does Knip work with my package manager?

Yes, Knip works with any package manager: npm, pnpm, Bun and Yarn are all
supported. It's easy to get started with any package manager.


Packages Docs Src Content Docs Typescript Unused Exports (`packages/docs/src/content/docs/typescript/unused-exports.md`)


title: Unused exports prev: false next: false

Finding unused exports in your JavaScript and TypeScript projects is one of
Knip's core features. And it comes with even more features to identify and
remove clutter to keep your projects in great shape.

Why are unused exports a problem?

Having unused exports in your codebase is problematic for several reasons:

  • They increase bundle sizes if not properly eliminated by tree-shaking.
  • They clutter the codebase and make it harder to navigate and understand.
  • They mislead developers into thinking certain code is used when it's not.
  • They make refactoring and maintaining the codebase more difficult.
  • They slow down tooling that analyze the codebase, such as bundlers, linters
    and type checkers.
  • They may represent dead code that is no longer needed but hasn't been cleaned
    up.

How do I find unused exports?

Knip is a powerful tool that can help you find and remove unused exports in your
JavaScript and TypeScript projects. It analyzes the codebase, identifies exports
that are not imported anywhere, and reports them.

Get started and install Knip to run it on your project. Knip will scan your
files and provide a detailed report of unused exports, and much more.

How does Knip identify unused exports?

Knip performs both static and dynamic analysis to determine which exports are
actually being used in your codebase. It looks at import statements, export
usage, and a lot more code patterns to identify unused exports.

Knip supports JavaScript and TypeScript projects, and handles both CommonJS
and ES Modules syntax.

Can Knip remove unused exports?

Yes, Knip not only finds unused exports but can also remove them for you. Run
Knip with the --fix flag to enable the auto-fix feature, and it will
modify your source code and remove the unused exports.

It's always recommended to review the changes made by Knip before committing
them to ensure no unintended modifications were made.

Can Knip handle large codebases?

Absolutely. Knip supports monorepos with workspaces to efficiently analyze
large monorepos. This makes it easier and more fun to manage and optimize large
multi-package projects.

Does Knip work with my favorite editor or IDE?

Knip is a command-line tool that runs independently of your editor or IDE.
However, if you run Knip inside an integrated IDE terminal, the report contains
file names and positions in a format IDEs like VS Code and WebStorm understand
to easily navigate around.

How is Knip different from ESLint for finding unused exports?

While linters like ESLint can find unused imports and variables within
individual files, Knip analyzes the entire project to determine which exports
are actually unused. By building a comprehensive module graph, Knip
identifies exports that are not imported or used anywhere in the codebase. This
allows Knip to catch unused exports and dead code that ESLint and other linters
would miss.

Also see Why isn't Knip an ESLint plugin?


Packages Docs Src Content Docs Writing A Plugin Argument Parsing (`packages/docs/src/content/docs/writing-a-plugin/argument-parsing.md`)


title: Argument Parsing

Some plugins have an arg object in their implementation. It's a way for
plugins to customize how command-line arguments are parsed for their tool's
executables. Argument parsing in plugins help Knip identify dependencies and
entry files from scripts.

Knip parses these arguments with a built-in parser. The options below customize
how a tool's scripts are parsed. Here's an example:

ts
const args = {
  args: (args) => args  // modify/filter the array of raw args
  config: true,         // shorthand to add `--config [config file]` (and shortcut `-c`) as a `config` file
  fromArgs: ['exec'],   // re-parse what's behind `exec` as a new script
};

export default {
  title: 'my-plugin',
  args
}

Also see type definitions and examples in existing plugins.

alias

Define aliases.

Example:

ts
{
  require: ['r'];
}

Also see nodeImportArgs.

args

Modify or filter arguments before parsing. For edge cases preprocessing is
useful, e.g. if the parser has trouble parsing or to modify/discard arguments.

Example:

ts
{
  args: (args: string[]) => args.filter(arg => arg !== 'omit');
}

binaries

Executables for the dependency.

Example:

ts
{
  binaries: ['tsc'];
}

Default: plugin name, e.g. for the ESLint plugin the value is ["eslint"]

boolean

Mark arguments as boolean. By default, arguments are expected to have string
values.

config

Define arguments that contain the configuration file path. Usually you'll want
to set aliases too. Use true for shorthand to set alias + string +
config.

Example:

ts
{
  config: true;
}

The tsup plugin has this. Now tsup --config tsup.client.json will have
tsup.client.json go through resolveConfig (also -c alias).

Example:

ts
{
  config: ['p'];
}

This will mark e.g. tsc -p tsconfig.lib.json as a configuration file and it
will be handled by resolveConfig of the (typescript) plugin.

fromArgs

Parse return value as a new script. Can be a an array of strings, or function
that returns an array of strings and those values will be parsed separately.

Example:

ts
{
  fromArgs: ['exec'];
}

Then this script:

sh
nodemon --exec "node index.js"

Will have "node index.js" being parsed as a new script.

nodeImportArgs

Set to true as a shorthand for this alias:

ts
{
  import: ['r', 'experimental-loader', 'require', 'loader']
}

Example:

ts
{
  nodeImportArgs: true;
}

positional

Set to true to use the first positional argument as an entry point.

Example:

ts
{
  positional: true;
}

The tsx plugin has this and "tsx script.ts" as a script will result in the
script.ts file being an entry point.

resolve

List of arguments to resolve to a dependency or entry file path.

Example:

ts
{
  resolve: ['plugin'];
}

Now for a script like "program --plugin package" this will result in
"package" being resolved as a dependency.

resolveInputs

Return inputs from parsed arguments

ts
{
  resolveInputs: (parsed: ParsedArgs) =>
    parsed['flag'] ? [toDependency('package')] : [];
}

string

Mark arguments as string. This is the default, but number-looking arguments are
returned as numbers.


Packages Docs Src Content Docs Writing A Plugin Inputs (`packages/docs/src/content/docs/writing-a-plugin/inputs.md`)


title: Inputs

You may have noticed functions like toDeferResolve and toEntry. They're a
way for plugins to tell what they've found and how Knip should handle those. The
more precise a plugin can be, the better it is for results and performance.
Here's an overview of all input type functions:

toEntry

An entry input is just like an entry in the configuration. It should either
be an absolute or relative path, and glob patterns are allowed.

toProductionEntry

A production entry input is just like an production in the configuration. It
should either be an absolute or relative path, and it can have glob patterns.

toProject

A project input is the equivalent of project patterns in the configuration.
It should either be an absolute or relative path, and (negated) glob patterns
are allowed.

toDependency

The dependency indicates the entry is a dependency, belonging in either the
"dependencies" or "devDependencies" section of package.json. Use it for
package names referenced from configuration, such as a plugin or transformer a
tool loads at build time.

Knip marks the package as referenced, and reports it as an unlisted dependency
if it isn't listed at all. Such a reference is development-only: in production
mode
the package is still marked as referenced, but an unlisted one is no
longer reported.

toProductionDependency

The production dependency indicates the entry is a production dependency,
expected to be listed in "dependencies". Unlike toDependency it's also
reported as unlisted in production mode.

toDeferResolve

The deferResolve input type is used to defer the resolution of a specifier.
This could be resolved to a dependency or an entry file. For instance, the
specifier "input" could be resolved to "input.js", "input.tsx",
"input/index.js" or the "input" package name. Local files are added as entry
files, package names are external dependencies.

If this does not lead to a resolution, the specifier will be reported under
"unresolved imports".

toDeferResolveEntry

The deferResolveEntry input type is similar to deferResolve, but it's used
for entry files only (not dependencies) and unresolved inputs are ignored. It's
different from toEntry as glob patterns are not supported.

toConfig

The config input type is a way for plugins to reference a configuration file
that should be handled by a different plugin. The specifier is a path to that
file.

For instance, angular.json has tsConfig and karmaConfig options pointing
to files the TypeScript and Karma plugins know how to handle, so the Angular
plugin hands them over:

ts
toConfig('typescript', 'tsconfig.app.json');
toConfig('karma', 'karma.conf.js');

toBinary

The binary input type isn't used by plugins directly, but by the shell script
parser (through the getInputsFromScripts helper). Think of GitHub Actions
workflow YAML files or husky scripts. Using this input type, a binary is
"assigned" to the dependency that has it as a "bin" in their package.json.

toAlias

The alias input type adds path aliases to the core module resolver. They're
added to compilerOptions.paths so the syntax is identical.

Options

When creating inputs from specifiers, an extra options object as the second
argument can be provided.

dir

The optional dir option assigns the input to a different workspace. For
instance, GitHub Action workflows are always stored in the root workspace, and
support working-directory in job steps. For example:

yaml
jobs:
  stylelint:
    runs-on: ubuntu-latest
    steps:
      - run: npx esbuild
        working-directory: packages/app

The GitHub Action plugin understands working-directory and adds this dir to
the input:

ts
toDependency('esbuild', { dir: 'packages/app' });

Knip now understands esbuild is a dependency of the workspace in the
packages/app directory.

optional

Use the optional flag to indicate the dependency is optional. Then, a
dependency won't be flagged as unlisted if it isn't.

isTypeOnly

Use isTypeOnly when only the types of a package are used, such as the packages
listed in compilerOptions.types. A @types/pkg dependency then satisfies the
reference on its own, without pkg itself being installed. Don't use it for
packages the tool actually loads, even if it does so only at build time.

allowIncludeExports

By default, exports of entry files such as src/index.ts or the files in
package.json#exports are not reported as unused. When using the
--include-entry-exports flag or isIncludeExports: true option, unused
exports on such entry files are also reported.

Exports of entry files coming from plugins are not included in the analysis,
even with the option enabled. This is because certain tools and frameworks
consume named exports from entry files, causing false positives.

The allowIncludeExports option allows the exports of entry files to be
reported as unused when using --include-entry-exports. This option is
typically used with the toProductionEntry input type.

Example:

ts
toProductionEntry('./entry.ts', { allowIncludeExports: true });

--- METRICS ---