dependency-cruiser
dependency-cruiser scans JavaScript, TypeScript, JSX, Vue, Svelte, CoffeeScript, and related source files, resolves their imports, and turns the result into an enforceable dependency graph. You describe forbidden, allowed, or required relationships in a configuration file, then run the CLI in CI like a structural linter. It can catch circular imports, unresolvable modules, undeclared packages, cross-layer dependencies, and isolated features that reach into one another. The same scan can produce text, JSON, Mermaid, D2, GraphViz, HTML, and other reports, so it serves both architecture enforcement and investigation.
The strongest JavaScript dependency-policy tool when a team is willing to encode its architecture and keep the resolver configuration honest. Skip it for a one-off cycle check or on older Node releases, where a narrower tool creates much less ceremony.
Use it if
- You need CI to reject imports that cross architectural boundaries, such as UI code reaching directly into persistence
- You want one tool to find cycles, orphan files, unresolved imports, and package.json dependency mistakes across a large JavaScript or TypeScript codebase
- You need dependency graphs that can be filtered, collapsed to package or folder level, and emitted as Mermaid, D2, GraphViz, JSON, or HTML
- You are adopting architecture rules gradually and need a baseline file that separates known violations from new ones
- Your project still runs Node 20 or older: version 18.1.1 declares support only for Node 22, Node 24, and Node 26 or newer
- You only want unused-file and unused-export detection: dependency-cruiser models relationships and can flag orphans, but Knip is built specifically for dead-code cleanup and covers exports and dependencies too
- You expect accurate TypeScript results without installing TypeScript beside the tool or pointing it at tsconfig.json; the FAQ says missing compiler discovery can leave dependencies unresolved or show only the first level
- You need a polished graph with no external tooling: GraphViz output still needs the dot executable, while D2 needs the d2 CLI, and large unfiltered graphs quickly become unreadable
- You want an architecture policy with almost no configuration: useful layer and feature rules are regular-expression based, so path conventions, exclusions, aliases, and generated files all need deliberate modeling
Setup reality
Install it locally as a development dependency with npm install --save-dev dependency-cruiser, then run npx depcruise --init. The initializer inspects the project, asks questions, and creates a .dependency-cruiser.js or .dependency-cruiser.cjs file; pure ESM projects need the .cjs form when the config uses module.exports. Version 18.1.1 will not install on the Node 20 line because its engine range is ^22 || ^24 || >=26. TypeScript, Vue, Svelte, CoffeeScript, and other non-plain-JavaScript analysis depends on the relevant compiler being installed where dependency-cruiser can find it. Run depcruise --info to confirm that detection. For TypeScript path aliases and type-only imports, pass or configure tsconfig.json and enable tsPreCompilationDeps when those edges matter. Webpack-specific aliases need its resolve configuration or equivalent enhanced-resolve options. The generated recommended rules often reveal existing cycles, orphan entry points, and unresolved aliases on the first run; use targeted exclusions or a known-violations baseline instead of switching rules off blindly. Text and JSON reporting need no system package, but an SVG pipeline using output type dot requires the separate GraphViz dot executable. Keep --include-only and doNotFollow scoped, especially in a monorepo, or the scan and graph can expand through far more files than intended.
Patterns
Install locally and generate a starter configinstall-and-init
npm install --save-dev dependency-cruiser
npx depcruise --init
npx depcruise srcVersion 18 requires Node 22, 24, or 26+. A local install is recommended so compiler discovery matches the project.
Reject circular dependenciesfail-on-cycles
module.exports = {
forbidden: [
{
name: 'no-circular',
severity: 'error',
from: {},
to: { circular: true },
},
],
options: { doNotFollow: { path: 'node_modules' } },
};Severity error gives the err reporter a nonzero exit code, which is what makes the rule useful in CI.
Keep the domain layer independent of infrastructureenforce-layers
module.exports = {
forbidden: [
{
name: 'domain-not-to-infrastructure',
comment: 'Domain code must not import adapters or database code',
severity: 'error',
from: { path: '^src/domain/' },
to: { path: '^src/(infrastructure|adapters)/' },
},
],
};Path restrictions are regular expressions matched against normalized module paths, so anchor them to avoid accidental substring matches.
Stop sibling features from importing each otherisolate-features
module.exports = {
forbidden: [
{
name: 'features-not-to-features',
severity: 'error',
from: { path: '(^src/features/)([^/]+)/' },
to: { path: '^$1', pathNot: '$1$2' },
},
],
};The $1 and $2 values come from capture groups in from.path; changing that expression can silently change what pathNot means.
Prevent production code from importing dev dependenciesblock-dev-dependencies
module.exports = {
forbidden: [
{
name: 'not-to-dev-dep',
severity: 'error',
from: { path: '^src/' },
to: { dependencyTypes: ['npm-dev'] },
},
],
};Exclude tests, stories, and build scripts in from.pathNot if they legitimately import packages from devDependencies.
Include TypeScript type-only and pre-compilation edgesinclude-type-imports
module.exports = {
forbidden: [],
options: {
tsConfig: { fileName: 'tsconfig.json' },
tsPreCompilationDeps: true,
doNotFollow: { path: 'node_modules' },
},
};TypeScript must be installed where dependency-cruiser can discover it; check with npx depcruise --info when imports are missing.
Render a filtered SVG dependency graphcreate-svg-graph
npx depcruise src \
--include-only '^src/' \
--output-type dot \
| dot -T svg > dependency-graph.svgThe final dot command comes from GraphViz and is not installed by dependency-cruiser. Filtering first keeps the graph legible.
Write a Mermaid graph for Markdowncreate-mermaid-graph
npx depcruise src \
--include-only '^src/' \
--output-type mermaid \
--output-to dependency-graph.mmdThe Mermaid reporter minifies names by default to stay within renderer input limits; it is less customizable than GraphViz output.
Inspect one module and its neighborsfocus-module
npx depcruise src \
--focus '^src/services/billing\.ts$' \
--focus-depth 2 \
--output-type textQuote regular expressions so the shell does not expand or reinterpret them differently across platforms.
Show modules affected by changes since maincheck-affected
npx depcruise src \
--affected main \
--include-only '^src/' \
--output-type textThe affected option is command-line only; the current type reference says it is ignored when placed in the API options or config file.
Adopt rules without accepting new violationsbaseline-existing-violations
npx depcruise src --output-type baseline \
--output-to .dependency-cruiser-known-violations.json
# Later CI runs ignore the recorded set but still fail on new errors
npx depcruise src --ignore-knownCommit the baseline and shrink it as violations are fixed; regenerating it on every CI run would hide regressions.
Cruise source files from an ESM scriptuse-programmatic-api
import { cruise } from 'dependency-cruiser';
const result = await cruise(['src'], {
includeOnly: '^src/',
doNotFollow: 'node_modules',
outputType: 'json',
});
if (result.exitCode !== 0) process.exitCode = result.exitCode;
console.log(result.output);The current API is asynchronous and the package root exposes an ESM import. Validation also requires validate: true and a ruleSet.
Alternatives
| Package | Registry | Pick it when |
|---|---|---|
| madge | npm | Choose it for quick circular-dependency checks and simple visual graphs when you do not need a full rule language |
| dpdm | npm | Choose it for a smaller TypeScript-focused cycle detector with straightforward command-line output |
| eslint-plugin-boundaries | npm | Choose it when architectural import rules should run inside the ESLint workflow developers already use |
| knip | npm | Choose it when the real goal is unused files, exports, and dependencies rather than graph policy |