Safeguard
Open Source

eslint-plugin-import and import-helpers: Order Your Imports, Catch Mistakes

eslint-plugin-import-helpers gives you fully configurable import ordering; eslint-plugin-import catches the real bugs — unresolved paths, phantom dependencies, cycles. Most codebases want both.

Safeguard Team
Product
7 min read

eslint-plugin-import-helpers exists to do one thing better than its bigger sibling: its order-imports rule lets you define import ordering with arbitrary regex groups, so your convention — framework first, then absolute aliases, then relative — is enforced and autofixed exactly as you designed it. The bigger sibling, eslint-plugin-import, is where the correctness rules live: unresolved paths, dependencies you use but never declared, circular imports. The two plugins are complementary, not competing, and the combination turns import statements from a bikeshedding topic into both a solved formatting problem and a quiet early-warning system for dependency mistakes that have real supply chain consequences. Here is how to configure each, and which rules pay rent.

Two plugins, two jobs

eslint-plugin-import (maintained under the import-js organization) ships dozens of rules across static analysis, helpfulness, and style. Its own import/order rule handles grouping and alphabetization and has grown steadily more configurable via pathGroups.

eslint-plugin-import-helpers (by Will Honey) was created as a supplement when import/order was less flexible than teams needed. Its single rule, order-imports, takes a groups array where each entry is a builtin category (module, parent, sibling, index) or any regex:

// eslint config (flat config, ESLint 9)
const importHelpers = require('eslint-plugin-import-helpers');

module.exports = [
  {
    plugins: { 'import-helpers': importHelpers },
    rules: {
      'import-helpers/order-imports': ['warn', {
        newlinesBetween: 'always',
        groups: [
          '/^react/',              // react and react-* first
          'module',                // other node_modules
          '/^@company\\//',        // internal scoped packages
          '/^~\\//',               // path alias imports
          ['parent', 'sibling', 'index'],
        ],
        alphabetize: { order: 'asc', ignoreCase: true },
      }],
    },
  },
];

The regex groups are the differentiator: import/order's pathGroups can approximate this, but import-helpers' model is simpler to reason about when your convention has many tiers. Both rules are autofixable, which is the property that makes ordering rules worth having at all — set to warn, run eslint --fix in the pre-commit hook, and no human ever sorts an import block again. On version support: import-helpers 2.0 added ESLint v9 compatibility, and eslint-plugin-import exposes flatConfigs.recommended for flat config, so both work on current toolchains. (The community fork eslint-plugin-import-x also exists with a faster release cadence and is API-compatible for most rules, if the original's pace becomes a constraint for you.)

Pick one ordering rule. Running import/order and import-helpers/order-imports together produces fix loops where each autofixer reorders the other's output.

The correctness rules are the actual value

Ordering is hygiene. These eslint-plugin-import rules catch bugs, and two of them are quietly security-relevant.

import/no-extraneous-dependencies errors when code imports a package that is not declared in package.json. Undeclared-but-resolvable imports — phantom dependencies — work on your machine because hoisting happened to place the package in reach, then break (or worse, resolve to something else) when an unrelated dependency update reshuffles the tree. In a workspace monorepo, a phantom dependency means your SBOM and vulnerability scans are auditing a manifest that does not match what the code actually loads. Configure it per file class:

'import/no-extraneous-dependencies': ['error', {
  devDependencies: ['**/*.test.ts', '**/*.config.js', 'scripts/**'],
  optionalDependencies: false,
}],

import/no-unresolved errors when an import path resolves to nothing. Beyond catching renamed-file breakage before runtime, it is your cheapest tripwire against dependency typos: import lodash from 'ladash' fails lint the moment it is written instead of at install time — and install time is exactly when a typosquatted package would execute its install script. Lint cannot replace registry-side protections, but failing fast on unresolvable names shrinks the window where a typo becomes an npm install of something malicious. Pair the lint rule with registry-side checks from an SCA tool like Safeguard, which evaluates the package you did install rather than the name you meant.

import/no-cycle flags circular imports, which manifest as undefined exports at runtime in CommonJS and as subtle initialization-order bugs in ESM. It is expensive — it walks the module graph — so scope it: set maxDepth (a small value catches the cycles that matter), and consider running it only in CI rather than in editors.

import/no-internal-modules and import/no-restricted-paths enforce architecture: which layers may import which. Teams use no-restricted-paths to keep server/ code out of client/ bundles — a genuine data-exposure control, since a casual import of a server constants file can carry secrets or internal endpoints into shipped JavaScript. If you have ever found an API key in a bundle with webpack-bundle-analyzer, this rule is the shift-left version of that discovery.

Resolver configuration: where setups go wrong

Most frustration with eslint-plugin-import traces to the resolver — the component that maps import strings to files. TypeScript paths, workspace packages, and bundler aliases all need the resolver to know about them, or no-unresolved drowns you in false positives (and gets turned off, taking its value with it):

npm install --save-dev eslint-import-resolver-typescript
settings: {
  'import/resolver': {
    typescript: { project: ['./tsconfig.json', './packages/*/tsconfig.json'] },
    node: true,
  },
},

The rule of thumb: if no-unresolved reports a path your bundler accepts, fix the resolver settings, never the rule severity. A disabled correctness rule is worse than an absent one, because it implies coverage that is not there.

Performance is the other recurring complaint on large repos. Mitigations that work: enable import/no-cycle and other graph-walking rules in CI only, use ESLint's caching (--cache), and let the TypeScript resolver do the heavy lifting once rather than configuring three resolvers that each re-walk node_modules.

A pragmatic combined setup

For a TypeScript monorepo, the configuration that teams converge on:

  • eslint-plugin-import with flatConfigs.recommended and flatConfigs.typescript as the base.
  • import/no-extraneous-dependencies, import/no-unresolved, import/no-self-import at error — these are bugs.
  • import/no-cycle at error in CI, off in editors.
  • eslint-plugin-import-helpers/order-imports at warn with autofix in the pre-commit hook — style should never block a human, only a robot.
  • import/order off (superseded by import-helpers).

That split — correctness blocks merges, style gets autofixed silently — keeps the lint signal trustworthy. When every violation that reaches a PR review is a real problem, people stop ignoring the linter, which is the whole game. Lint is also the first tier of a defense stack, not the last: it validates the imports you wrote, while dependency scanning validates the packages behind them — the Safeguard Academy covers how those layers hand off to each other if you are building the pipeline out.

FAQ

What is the difference between eslint-plugin-import and eslint-plugin-import-helpers?

eslint-plugin-import is a broad ruleset covering import correctness (unresolved paths, extraneous dependencies, cycles) plus its own ordering rule. eslint-plugin-import-helpers provides one rule, order-imports, with a simpler and more flexible grouping model using arbitrary regexes. Use import for correctness, import-helpers for ordering, and never both ordering rules at once.

Does eslint-plugin-import-helpers work with ESLint 9 flat config?

Yes — version 2.0 added ESLint v9 support, and eslint-plugin-import exposes flatConfigs.recommended/flatConfigs.typescript for flat config setups.

Can import lint rules improve security?

Modestly but genuinely: no-extraneous-dependencies keeps your manifest honest so SBOMs and scanners audit reality, no-unresolved fails fast on typoed package names before anything installs, and no-restricted-paths keeps server-only code (and its secrets) out of client bundles.

Why does import/no-unresolved flag paths that build fine?

The ESLint resolver does not automatically know about TypeScript paths, workspace links, or bundler aliases. Install and configure eslint-import-resolver-typescript (or the node resolver options) so lint resolution matches build resolution — fix the resolver, not the rule.

Never miss an update

Weekly insights on software supply chain security, delivered to your inbox.