An ESLint plugin that enforces layer-based import direction for layered architectures (FSD, custom layer structures, etc.).
Core value: Just provide a layers array and all rules are automatically applied.
"import-layers/layers": ["error", {
layers: ["domains", "features", "shared"]
}]npm install --save-dev eslint-plugin-import-layers// eslint.config.js
import importLayers from "eslint-plugin-import-layers";
export default [
{
plugins: { "import-layers": importLayers },
rules: {
"import-layers/layers": [
"error",
{
layers: ["domains", "features", "shared"],
aliases: { "@": "src" },
},
],
},
},
];// .eslintrc.json
{
"plugins": ["import-layers"],
"rules": {
"import-layers/layers": [
"error",
{
"layers": ["domains", "features", "shared"],
"aliases": { "@": "src" }
}
]
}
}The layers array treats earlier items as upper layers and later items as lower layers.
layers: ["domains", "features", "shared"];
// domains(0) > features(1) > shared(2)A lower layer cannot import from an upper layer.
// ❌ features(1) importing from domains(0)
import { UserEntity } from "@/domains/user"; // in features/auth/...
// ✅ features(1) importing from shared(2)
import { Button } from "@/shared/ui"; // in features/auth/...Different slices within the same layer cannot import from each other.
// ❌ features/auth importing from features/cart
import { CartItem } from "@/features/cart"; // in features/auth/...
// ✅ Relative imports within the same slice are allowed
import { validate } from "./utils"; // in features/auth/...Note: These rules apply to all import forms —
import,export ... from, dynamicimport(), andrequire().
interface Options {
// Layer list. Earlier = upper layer, later = lower layer. (required)
layers: string[];
// Path alias mapping.
// @default {}
aliases?: Record<string, string>;
// Automatically read tsconfig.json paths for alias resolution.
// When true, the aliases option is ignored.
// @default false
autoReadTsConfig?: boolean;
// Import pairs to allow as exceptions to the rules.
// @default []
allowedImports?: Array<{ from: string; to: string }>;
// Layers where cross-slice imports are permitted.
// @default []
allowCrossSlice?: string[];
}"import-layers/layers": [
"error",
{
layers: ["domains", "features", "shared"],
aliases: { "@": "src" },
allowedImports: [
{ from: "features/auth", to: "features/user" },
],
},
]"import-layers/layers": [
"error",
{
layers: ["domains", "features", "shared"],
aliases: { "@": "src" },
allowCrossSlice: ["shared"],
},
]"import-layers/layers": [
"error",
{
layers: ["domains", "features", "shared"],
autoReadTsConfig: true,
},
]Errors include actionable guidance, not just violation notices.
'features' cannot import from upper layer 'domains'. If this dependency is unavoidable, add an exception to allowedImports.
Within 'features' layer, 'auth' cannot import from 'cart'. If this dependency is unavoidable, add an exception to allowedImports.
MIT