eslint-plugin-unused-imports is an ESLint plugin that splits the detection of unused imports out from the built-in no-unused-vars rule and, unlike the base rule, provides an autofixer that actually removes the dead import statements for you. If you have wanted ESLint to not just flag eslint remove unused imports situations but clean them up automatically on save or in CI, this plugin is the standard answer. The core ESLint rule reports unused imports; it will not delete them, and that gap is exactly what this plugin fills.
The package is maintained by sweepline, and its version line tracks ESLint and TypeScript-ESLint closely, which is the first thing to get right before anything else.
Why the base ESLint rule is not enough
ESLint ships no-unused-vars, which reports variables and imports that are declared but never used. It is a good rule, and for a long time it was all most projects had. But it treats an unused import the same as any other unused variable: it warns, and it stops there. It has no autofix for imports, because removing an import is a slightly more delicate edit than removing a local variable, and the core rule stays conservative.
That leaves you manually deleting imports one at a time every time you refactor — tedious, error-prone, and exactly the kind of chore developers skip under deadline pressure. The eslint unused imports problem is therefore not a detection problem; it is a remediation problem. You already know the imports are dead; you want them gone without hand-editing.
eslint-plugin-unused-imports solves this by separating the two concerns. It provides a rule, unused-imports/no-unused-imports, that specifically targets import statements and ships with an autofixer, so eslint --fix strips them. It also re-exposes the general unused-variable logic as unused-imports/no-unused-vars for the non-import cases, letting you turn off the base rule entirely and manage both through one consistent plugin.
Version compatibility — check this first
The single most common source of frustration with this plugin is a version mismatch, because it is tightly coupled to your ESLint and @typescript-eslint versions. Install the wrong major and you get confusing plugin-load errors.
The compatibility maps roughly like this:
- Version 4.x targets ESLint 9. The 4.1+ line supports
@typescript-eslintversions 5 through 8; 4.0.x pairs specifically with@typescript-eslint8. - Version 3.x targets ESLint 8 with
@typescript-eslint6 to 7. - Version 2.x targets ESLint 8 with
@typescript-eslint5. - Version 1.x is for the much older ESLint 6 and 7.
The current release at the time of writing is in the 4.4.x line. If you are on modern tooling with ESLint 9 and the flat config format, you want the 4.x line. Getting this pairing right before you touch configuration saves the "Failed to load plugin" errors that dominate the issue tracker.
Install it as a dev dependency:
npm install --save-dev eslint-plugin-unused-imports
Configuring it on ESLint 9 (flat config)
ESLint 9 uses the flat config format (eslint.config.js), which is where most new setup confusion lives. Here is a working configuration that turns off the base rule and delegates both import and variable checks to the plugin:
import unusedImports from "eslint-plugin-unused-imports";
export default [
{
plugins: {
"unused-imports": unusedImports,
},
rules: {
"no-unused-vars": "off",
"unused-imports/no-unused-imports": "error",
"unused-imports/no-unused-vars": [
"warn",
{
vars: "all",
varsIgnorePattern: "^_",
args: "after-used",
argsIgnorePattern: "^_",
},
],
},
},
];
Two things to note. First, no-unused-vars is turned off so the base rule and the plugin do not both report the same thing. Second, the varsIgnorePattern and argsIgnorePattern set to ^_ is a common convention: prefix a genuinely-intentional unused variable with an underscore (_unused) and the linter leaves it alone. This matters for function signatures where you must accept an argument you do not use.
If you use TypeScript, install and configure both @typescript-eslint/eslint-plugin and @typescript-eslint/parser — the unused-imports plugin relies on the TypeScript parser to understand type-only imports correctly. And if you write React, enable react/jsx-uses-react and react/jsx-uses-vars from eslint-plugin-react, or every component import will be flagged as unused because JSX usage is not visible to the base analysis.
Wiring it into the workflow
Detection without enforcement decays. Make the plugin do its job in three places.
In the editor, enable fix-on-save. In VS Code, setting the ESLint extension to run fixable rules on save means dead imports vanish the moment you remove the last usage — the ideal feedback loop, since the code is cleaned while it is fresh in your head.
In pre-commit, run eslint --fix on staged files through a tool like lint-staged so nothing dead reaches a commit. This keeps history clean and stops the "remove unused imports" noise from cluttering later diffs.
In CI, run ESLint without --fix so the pipeline fails on any dead import rather than silently rewriting code on the server. The editor and pre-commit hooks are where fixes happen; CI is where you verify they happened. That separation prevents the confusing situation where CI mutates code and a developer's local tree diverges.
Is unused code actually a security concern?
It is a modest but real one, and worth being honest about rather than overstating. Dead imports do not directly create vulnerabilities, but they do two things that matter for a security-conscious team.
They keep dependencies in the graph that nothing uses, which enlarges your attack surface and your dependency-audit burden for no benefit. An import you forgot to remove keeps a package "in use" from a tooling perspective, so it keeps showing up in scans, keeps needing updates, and keeps being a thing an attacker could theoretically reach. Aggressively removing dead imports helps surface genuinely unused dependencies you can then drop entirely — and fewer dependencies is a smaller surface for software composition analysis to worry about.
They also reduce noise, and noise is a security cost. A codebase where every import is live is one where a reviewer notices an unexpected or suspicious import immediately. In a codebase littered with dead imports, an unusual one hides in the clutter — which is exactly the kind of camouflage a malicious or typosquatted dependency benefits from. Clean imports are not a control by themselves, but they make the reviews that are controls sharper.
FAQ
How is eslint-plugin-unused-imports different from no-unused-vars?
The base no-unused-vars rule detects unused imports but cannot remove them. eslint-plugin-unused-imports adds a dedicated no-unused-imports rule with an autofixer, so eslint --fix deletes the dead import statements automatically instead of only warning.
Which version do I need for ESLint 9?
The 4.x line (currently 4.4.x). Version 4.1+ supports @typescript-eslint 5 through 8, while 4.0.x pairs with @typescript-eslint 8 specifically. Versions 3.x and below are for ESLint 8 and earlier — installing those on ESLint 9 causes plugin-load errors.
Why are all my imports flagged as unused in a React project?
Because JSX usage is invisible to the base analysis unless the React plugin rules are enabled. Install eslint-plugin-react and turn on react/jsx-uses-react and react/jsx-uses-vars, and component imports will be recognized as used.
Should I run the autofix in CI?
No. Run eslint --fix in your editor and pre-commit hook, but run ESLint without --fix in CI so the pipeline fails on dead imports rather than silently rewriting code on the server. That keeps CI a verifier, not a mutator, and avoids local/remote divergence.