CLI tool
nadeesha/ts-prune avatar
nadeesha/ts-prune

The string false does not disable a flag

Find unused exports in a typescript project. 🛀

2,064 stars67 forksTypeScriptMIT

At a glance

What is it?
ts-prune reports exports with no detected use, without changing any file. Its configuration accepts booleans for two flags while still accepting legacy string values, and the string "false" does not turn the flag off, which is a trap worth knowing before you wire it into CI.
Who is it for?
ts-prune suits a TypeScript codebase with a growing pile of exports nobody imports, and a team willing to treat its output as a review list rather than as a deletion plan. It does not suit a library whose public surface is consumed through conventions, reflection or a computed import path.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 20 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

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

Editorial analysis

ignore filters the report, skip changes the analysis

Two filtering options look similar and do different things, and the README draws the line explicitly. The --ignore option, or -i, hides matching output lines. The --skip option, or -s, excludes matching files from the analysis itself and from the references counted as uses. The example given is what makes the difference concrete: skipping tests means an export used only by tests now appears as unused, because the file using it was removed from the analysis rather than merely from the display. Both take JavaScript regular expressions, not globs, which is the other thing to check when a pattern silently matches nothing. A third option, -u or --unusedInModule, hides exports that are marked used in module, a marker applied to exports referenced within their own file. And -e or --error is the one that changes exit status, failing with status 1 when findings remain after filtering. Without it, findings do not cause a nonzero exit, though configuration and execution errors still fail the command.

The string false does not disable a flag

Configuration files are where this tool has a sharp edge, and the README documents it in one sentence. In configuration files you should use booleans for error and unusedInModule, where false disables them. Legacy string values remain accepted, but the string "false" does not disable a flag. That distinction is easy to miss and produces a confusing result: a config that reads as if it turned a check off while leaving it on. Options are applied in a defined order, defaults first, then discovered configuration, then explicitly supplied CLI options, so a command line flag beats a config file and a config file beats the built-in default. The practical habit is to keep booleans as booleans in every configuration file you write, and to verify the config you think you have by running the tool once and looking at what it reports.

Entry points are decided by the files array, not include

There are two ways to mark a file as public and they are not equivalent. Files explicitly listed in the selected tsconfig's files array are treated as public entrypoints, and their exports are excluded from the report. Files selected through include are analyzed normally, so an export from a file that include picks up is still a candidate even if nothing imports it. That distinction is the difference between configuring your library correctly and configuring it noisily, and it is not something the tool guesses. Two escape hatches sit alongside it. A comment placed immediately before a declaration, written as // ts-prune-ignore-next, excludes that export, and the example is an exported interface in a file called PublicApi. Uses outside the configured project are invisible to the tool by construction, so anything consumed by another package has to be marked as an entrypoint or ignored explicitly.

Discovery walks up to the home directory, not past it

There are more configuration file names than most tools support, and the resolution rules matter in monorepos:

json
{
  "ts-prune": {
    "project": "tsconfig.build.json",
    "error": true
  }
}

That block is the package.json form, and the same settings work in .ts-prunerc.json, in a .ts-prunerc holding JSON or YAML, in .ts-prunerc.yaml and .ts-prunerc.yml, and in JavaScript, CommonJS or TypeScript files such as .ts-prunerc.cjs, where a TypeScript config uses export default rather than module.exports. Discovery starts in the working directory and searches parent directories, stopping when it reaches the home directory or the filesystem root. That bound is deliberate, so a repository config never inherits something from a parent outside your checkout. Within a single directory the package.json property takes precedence over the standard files, and project paths resolve from the working directory, so running from a subdirectory changes what is analyzed unless you pass --project.

Node 22.18 is the floor, and the npm badge says the same

The requirement appears twice, in slightly different words, and both agree. The Node.js requirement badge in the header reads node >=22.18.0, and the requirements section says Node.js 22.18 or newer plus a TypeScript project with a tsconfig.json, with JavaScript files supported when allowJs is enabled. A floor that high is worth noting because it rules out the older Long Term Support lines most projects still standardize on, so this is a tool for a repository that has already chosen a recent Node baseline rather than one that can be dropped into anything. Installation is the usual three shapes, npm install --save-dev ts-prune, yarn add --dev ts-prune or pnpm add --save-dev ts-prune, all of which place it in dev dependencies where a reporting tool belongs, and the run command from the project root is npx ts-prune. A different configuration is selected with --project, for example tsconfig.build.json.

Output format is one line per finding

The report format is deliberately plain so it pipes into other tools. Each line is file:line - exportName, and the worked example makes it concrete: with a math module exporting add, subtract and multiply, and an app that imports only add, the output is subtract and multiply on lines two and three of the math file. Three details modify that output. Exports referenced inside their own file are marked used in module, and --unusedInModule hides them rather than removing the concept. Re-exports may show undefined as the line number when the declaration lives in another file, which is worth recognizing as a re-export rather than treating as a bug in the tool. The tool reports and never edits, which is stated in the opening paragraph and is the reason it is safe to run on an unfamiliar repository: nothing is removed until a human decides to remove it.

Where ts-prune is the wrong tool

Four cases follow directly from the limitations the project lists. The first is a library consumed through conventions: frameworks and build tools may consume exports through conventions or reflection that static analysis cannot detect, so a clean run proves nothing about a package's public API. The second is dynamic import patterns: imports with computed paths cannot be resolved reliably, and a resolved literal dynamic import such as import('./module') conservatively marks every export in that module as used, which hides real findings behind a safe default. The third is code relying on namespace uses that cannot be tracked, side-effect imports, or wildcard re-exports, any of which will keep unused exports out of the report entirely. The fourth is a monorepo where packages are consumed across project boundaries, since uses outside the configured project are invisible by design. In each case the tool is behaving correctly and the conclusion is that you have to mark entrypoints and ignore exports by hand, which is the ongoing cost of running it rather than a one-time cleanup.

Editorial conclusion

ts-prune suits a TypeScript codebase with a growing pile of exports nobody imports, and a team willing to treat its output as a review list rather than as a deletion plan. It does not suit a library whose public surface is consumed through conventions, reflection or a computed import path. Before adopting, add --error only after clearing the first batch of findings, list entrypoints in the tsconfig files array rather than relying on include, and use booleans rather than the string "false" in your config file.

Frequently asked questions

What does ts-prune need to run?

Node.js 22.18 or newer and a TypeScript project with a tsconfig.json. JavaScript files are supported when allowJs is enabled in that configuration.

What is the difference between --ignore and --skip?

--ignore hides matching output lines, while --skip excludes matching files from the analysis and from the references counted as uses. Skipping tests therefore makes exports used only by tests appear as unused.

How do I stop ts-prune failing a build?

Use the --error flag, which exits with status 1 if any findings remain after filtering. Without it, findings do not cause a nonzero exit, although configuration and execution errors still fail the command.

How do I mark a file as a public entry point?

List it explicitly in the files array of the selected tsconfig. Files pulled in through include are analyzed normally, so their exports are still reported when nothing uses them.

Why does my ts-prune config not turn off a flag?

In configuration files, error and unusedInModule should be booleans, where false disables them. Legacy string values are still accepted, but the string "false" does not disable a flag.

Does ts-prune delete anything?

No. It reports exports with no detected use and changes no files, and the README asks you to review findings before deleting code, because frameworks, reflection and computed imports can all hide real uses.

Official sources

  1. Issues
  2. License: MIT
  3. nadeesha/ts-prune on GitHub
  4. README
  5. Releases