ESLint plugin for enforcing Feature-Sliced Design (FSD) architecture
npm install eslint-plugin-fsd-lint



> It supports ESLint 9+ and the new Flat Config system.
eslint-plugin-fsd-lint is an ESLint plugin that enforces best practices for Feature-Sliced Design (FSD) architecture.
It is fully compatible with ESLint 9+ and follows the modern Flat Config format, ensuring seamless integration into modern JavaScript and TypeScript projects.
- Flat Config support: Fully compatible with ESLint 9+ and the new Flat Config system.
- Strict FSD compliance: Prevents architectural violations in feature-based project structures.
- Improves maintainability: Encourages clear module separation and dependency control.
- Ensures consistent code quality: Standardizes import patterns and best practices.
- Cross-platform compatibility: Works seamlessly on both Windows and Unix-based systems.
- Flexible folder naming: Supports custom folder naming patterns (e.g., 1_app, 2_pages).
- Multiple alias formats: Supports both @shared and @/shared import styles.
- Comprehensive test coverage: Thoroughly tested with real-world scenarios and edge cases.
Feature-Sliced Design (FSD) is a modern architecture pattern that provides a structured approach to organizing frontend applications.
This plugin enforces key FSD principles such as proper layer separation, import restrictions, and dependency management,
helping developers build scalable and maintainable codebases.
---
You can install eslint-plugin-fsd-lint via npm or pnpm:
``shell`
npm install --save-dev eslint-plugin-fsd-lint
`shell`
pnpm add -D eslint-plugin-fsd-lint
This plugin requires ESLint 9+ to work properly.
Make sure you have ESLint installed in your project:
`shell`
npm install --save-dev eslint
> š” Tip: If you're using a monorepo with multiple packages, install eslint-plugin-fsd-lint at the root level to share the configuration across all workspaces.
---
eslint-plugin-fsd-lint is designed for ESLint 9+ and works seamlessly with the Flat Config system. eslint.config.mjs
To use it in your project, add the following configuration to your :
`js
import fsdPlugin from 'eslint-plugin-fsd-lint';
export default [
// Use the recommended preset
fsdPlugin.configs.recommended,
// Or configure rules individually
{
plugins: {
fsd: fsdPlugin,
},
rules: {
// Enforces FSD layer import rules (e.g., features cannot import pages)
'fsd/forbidden-imports': 'error',
// Disallows relative imports between slices/layers, use aliases (@)
// Allows relative imports within the same slice by default (configurable)
'fsd/no-relative-imports': 'error',
// Enforces importing only via public API (index files)
'fsd/no-public-api-sidestep': 'error',
// Prevents direct imports between slices in the same layer
'fsd/no-cross-slice-dependency': 'error',
// Prevents UI imports in business logic layers (e.g., entities)
'fsd/no-ui-in-business-logic': 'error',
// Forbids direct import of the global store
'fsd/no-global-store-imports': 'error',
// Enforces import order based on FSD layers
'fsd/ordered-imports': 'warn',
},
},
];
`
Key Principles Enforced by Default:
- Layered Imports: Higher layers cannot import from lower layers (e.g., features cannot import pages).no-cross-slice-dependency
- Slice Isolation: Slices within the same layer should not directly import each other ().index.js
- Public API Usage: Imports from other slices/layers must go through their public API ( or index.ts). Direct internal imports are forbidden (no-public-api-sidestep).@
- Absolute Paths (Aliases): Use absolute paths (configured via aliases like ) for imports between different slices or layers. Relative paths are generally disallowed (no-relative-imports).../lib
- Relative Paths within Slices: Relative paths are allowed for imports _within the same slice_ (e.g., importing a helper from inside a feature). This is configurable via the allowSameSlice option in no-relative-imports (default: true).
The plugin provides three pre-defined configurations for different strictness levels:
`js
import fsdPlugin from 'eslint-plugin-fsd-lint';
export default [
// Standard recommended configuration
fsdPlugin.configs.recommended,
// Strict configuration (all rules as error)
// fsdPlugin.configs.strict,
// Base configuration (less strict)
// fsdPlugin.configs.base,
];
`
You can customize the behavior of the rules with advanced options:
`js
import fsdPlugin from 'eslint-plugin-fsd-lint';
export default [
{
plugins: {
fsd: fsdPlugin,
},
rules: {
// Configure alias format and folder patterns
'fsd/forbidden-imports': [
'error',
{
// Support for @shared or @/shared import styles
alias: {
value: '@',
withSlash: false, // Use true for @/shared format
},
// Support for numbered folder prefixes
folderPattern: {
enabled: true,
regex: '^(\\d+_)?(.*)',
extractionGroup: 2,
},
},
],
// Other rules...
},
},
];
`
Here's how an FSD-compliant project might look:
`plaintext
src/
āāā app/ (or 1_app/)
ā āāā providers/
ā āāā store.js
ā āāā index.js // Public API for app
ā
āāā processes/ (or 2_processes/)
ā āāā auth/
ā āāā onboarding/
ā
āāā pages/ (or 3_pages/)
ā āāā HomePage/
ā ā āāā ui/ // UI components for HomePage
ā ā āāā index.ts // Public API for HomePage
ā āāā ProfilePage/
ā
āāā widgets/ (or 4_widgets/)
ā āāā Navbar/
ā ā āāā ui/
ā ā āāā index.ts // Public API for Navbar
ā āāā Sidebar/
ā
āāā features/ (or 5_features/)
ā āāā login/
ā ā āāā ui/ // UI components for login feature
ā ā āāā model/ // Business logic for login
ā ā āāā index.ts // Public API for login feature
ā āāā registration/
ā
āāā entities/ (or 6_entities/)
ā āāā user/
ā ā āāā ui/
ā ā āāā model/
ā ā āāā index.ts // Public API for user entity
ā āāā post/
ā
āāā shared/ (or 7_shared/)
ā āāā ui/ // Reusable UI components
ā ā āāā Button/ // Example: Button component
ā āāā lib/ // Utility functions, helpers
ā āāā config/ // Shared configuration
ā āāā index.ts // Public API for shared layer (optional)
`
Import Examples based on Structure:
- Allowed (Alias Import - Cross Slice/Layer):
`javascript`
// features/login/ui/LoginForm.tsx
import { Button } from '@shared/ui/Button'; // OK: Feature uses Shared UI via alias
import { getUser } from '@entities/user'; // OK: Feature uses User entity via public API
`
- Allowed (Relative Import - Same Slice):
javascript`
// features/login/ui/LoginForm.tsx
import { validateInput } from '../lib/validation'; // OK: Relative import within the 'login' feature slice
`
- Disallowed (Relative Import - Cross Slice/Layer):
javascript`
// features/login/ui/LoginForm.tsx
import { config } from '../../../app/config'; // BAD: Relative path to different layer
import { User } from '../../entities/user/model/types'; // BAD: Relative path + Public API sidestep
`
- Disallowed (Public API Sidestep):
javascript`
// features/login/ui/LoginForm.tsx
import { userSlice } from '@entities/user/model/slice'; // BAD: Importing internal module directly
`
- Disallowed (Cross-Slice Dependency):
javascript`
// features/login/model/auth.ts
import { startRegistration } from '@features/registration'; // BAD: 'login' feature directly imports 'registration' feature
> š” Tip: Sticking to these import rules, especially using aliases and respecting public APIs, makes your codebase much easier to refactor and maintain.
---
This plugin provides a set of ESLint rules that enforce Feature-Sliced Design (FSD) best practices.
Each rule helps maintain a clear module structure, enforce import constraints, and prevent architectural violations.
| Rule | Description |
| --------------------------------- | ------------------------------------------------------------------------------------------------------------ |
| fsd/forbidden-imports | Prevents imports from higher layers and cross-imports between slices. |
| fsd/no-relative-imports | Disallows relative imports across different slices or layers. Allows relative imports within the same slice. |
| fsd/no-public-api-sidestep | Prevents direct imports from internal modules, enforcing public API usage. |
| fsd/no-cross-slice-dependency | Disallows direct dependencies between slices in the same layer (applies to all layers, not just features). |
| fsd/no-ui-in-business-logic | Prevents UI imports inside business logic layers (e.g., entities). |store
| fsd/no-global-store-imports | Forbids direct imports of global state (). |
| fsd/ordered-imports | Enforces import grouping by layer. |
---
Prevents imports from higher layers and cross-imports between slices.
ā
Allowed: features can import from entities or shared features
ā Not Allowed: importing directly from app
`js
// ā Incorrect (feature importing from app)
import { config } from '../../app/config';
// ā
Correct (feature importing from entities/shared)
import { getUser } from '../../entities/user';
import { formatCurrency } from '../../shared/utils';
`
Disallows relative imports across different slices or layers.
ā
Allowed: Using project-defined aliases or relative imports within same slice
ā Not Allowed: Relative imports between different slices
`javascript
// ā Incorrect (relative import across different slices)
import { fetchUser } from '../another-slice/model/api';
// ā
Correct (relative import within the same slice)
import { fetchData } from '../model/api';
// ā
Correct (alias import across slices or layers)
import { Button } from '@shared/ui/Button';
// Also supports @/shared format
import { Button } from '@/shared/ui/Button';
`
#### Available options:
`javascript
// In your eslint.config.js:
'fsd/no-relative-imports': ['error', {
// Allow relative imports within the same slice (enabled by default)
allowSameSlice: true,
// Allow type-only imports to use relative paths (disabled by default)
allowTypeImports: false,
// Patterns for test files that can ignore this rule
testFilesPatterns: ['\\.test\\.', '\\.spec\\.'],
// Patterns for imports to ignore
ignoreImportPatterns: []
}]
`
Prevents direct imports from internal modules of features, widgets, or entities.
ā
Allowed: Importing from index.ts (public API) or segment level
ā Not Allowed: Importing internal files within segments
`javascript
// ā
Correct (importing via public API)
import { authSlice } from '../../features/auth';
import { userModel } from '@entities/user/model'; // Segment level is allowed
// ā Incorrect (direct internal import)
import { authSlice } from '../../features/auth/slice.ts';
import { userSlice } from '@entities/user/model/slice'; // Internal file not allowed
`
Note: Any segment name is allowed (not limited to model/ui/api/lib). This provides flexibility for teams to define their own segment structure.
Prevents direct dependencies between slices in the same layer (applies to all layers, not just features).
ā
Allowed: Communication via lower layers
ā Not Allowed: Direct imports between different slices in the same layer
`javascript
// ā Incorrect (slice importing from another slice in the same layer)
import { processPayment } from '../../features/payment';
// ā
Correct (using entities/shared as an intermediary)
import { PaymentEntity } from '../../entities/payment';
// ā Also incorrect (entities slice importing from another entities slice)
import { Product } from '../../entities/product';
// This rule now applies to all layers, not just features!
`
Prevents UI imports inside business logic layers (e.g., entities).
ā
Allowed: UI should only be used inside widgets or pages
ā Not Allowed: entities importing UI components
`javascript
// ā Incorrect (entity importing widget)
import { ProfileCard } from '../../widgets/ProfileCard';
// ā
Correct (widget using entity data)
import { getUser } from '../../entities/user';
`
Forbids direct imports of global state (store).
ā
Allowed: Using useStore or useSelector
ā Not Allowed: Direct imports of the store
`javascript
// ā Incorrect (direct import of store)
import { store } from '../../app/store';
// ā
Correct (using hooks)
import { useStore } from 'zustand';
import { useSelector } from 'react-redux';
`
Enforces import grouping by layer.
ā
Allowed: Grouping imports by layer
ā Not Allowed: Mixed import order
`javascript
// ā Incorrect (random import order)
import { processPayment } from '../features/payment';
import { getUser } from '../entities/user';
import { formatCurrency } from '../shared/utils';
import { loginUser } from '../features/auth';
import { Header } from '../widgets/Header';
import { useStore } from '../app/store';
// ā
Correct (layered grouping)
import { useStore } from '../app/store'; // App
import { loginUser } from '../features/auth'; // Features
import { processPayment } from '../features/payment';
import { getUser } from '../entities/user'; // Entities
import { formatCurrency } from '../shared/utils'; // Shared
import { Header } from '../widgets/Header'; // Widgets
`
> š” Tip: Use npx eslint --fix to automatically reorder imports according to FSD layers.
---
Certain rules in eslint-plugin-fsd-lint support automatic fixing using ESLint's --fix option.
This allows developers to quickly resolve violations without manual code adjustments.
The following rules can be automatically fixed:
| Rule | Description |
| ----------------------- | ------------------------------------------------------------------------ |
| fsd/ordered-imports | Automatically sorts imports based on Feature-Sliced Design (FSD) layers. |
To apply automatic fixes to your project, simply run:
`shell`
npx eslint --fix your-file.js
Or, to fix all files in your project:
`shell`
npx eslint --fix .
ā Before (fsd/ordered-imports violation)
`javascript`
import { processPayment } from '../features/payment';
import { getUser } from '../entities/user';
import { formatCurrency } from '../shared/utils';
import { loginUser } from '../features/auth';
import { Header } from '../widgets/Header';
import { useStore } from '../app/store';
ā After (npx eslint --fix applied)
`javascript
import { useStore } from '../app/store'; // App
import { loginUser } from '../features/auth'; // Features
import { processPayment } from '../features/payment';
import { getUser } from '../entities/user'; // Entities
import { formatCurrency } from '../shared/utils'; // Shared
import { Header } from '../widgets/Header'; // Widgets
`
> š” Tip: fsd/ordered-imports ensures a clean and structured import order based on FSD layers.
---
The plugin now works seamlessly on both Windows and Unix-based systems by normalizing file paths internally.
You can now use numbered prefixes or other naming conventions for your folders:
`js`
// Configure folder pattern support
"fsd/forbidden-imports": ["error", {
folderPattern: {
enabled: true,
regex: "^(\\d+_)?(.*)",
extractionGroup: 2
}
}]
This allows using structures like:
``
src/
1_app/
2_pages/
3_widgets/
4_features/
5_entities/
6_shared/
The plugin now supports both @shared and @/shared import styles:
`js`
// Configure alias format
"fsd/forbidden-imports": ["error", {
alias: {
value: "@",
withSlash: false // Use true for @/shared format
}
}]
The no-cross-slice-dependency rule now applies to all layers by default, not just features:
`js`
// Restrict to features layer only (legacy behavior)
"fsd/no-cross-slice-dependency": ["error", {
featuresOnly: true
}]
Multiple configuration presets are now available:
- recommended - Standard recommended settingsstrict
- - Maximum enforcementbase
- - Less strict settings for easy adoption
The plugin now includes extensive test cases for all rules, covering:
- Basic import scenarios
- Edge cases and complex patterns
- Path variations (Windows, Unix, mixed)
- Custom configurations
- Real-world usage examples
The plugin now properly handles various path alias formats:
`javascript`
// Both formats are supported
import { UserCard } from '@entities/user';
import { UserCard } from '@/entities/user';
All rules now support dynamic imports:
`javascript
// Valid dynamic imports
const UserCard = await import('@entities/user');
const { UserCard } = await import('@entities/user');
// Invalid dynamic imports (will be caught by rules)
const UserCard = await import('@entities/user/ui');
`
All rules are now thoroughly tested with:
- Basic import scenarios
- Edge cases and complex patterns
- Path variations (Windows, Unix, mixed)
- Custom configurations
- Real-world usage examples
- Path alias formats
- Dynamic import patterns
---
We welcome contributions to improve eslint-plugin-fsd-lint`!
If you have an idea for a new rule or an improvement, feel free to submit a Pull Request.
Check out our contribution guide.
---
This project is licensed under the MIT License.
See the LICENSE file for details.