external
Enforce allowed external dependencies by element type.
boundaries/external is kept for backward compatibility but is deprecated and will be removed in a future major version. Use boundaries/dependencies with the to.module sub-selector instead.
It keeps working without changes; you will see a deprecation warning in your console:
Rule "boundaries/external" is deprecated and will be removed in future versions. Please migrate to the "boundaries/dependencies" rule with appropriate selectors.
Read the migration guide below for conversion examples, and the v6 to v7 migration guide for the full picture.
This rule builds on the same base configuration documented in Policies (a default plus a rules array). The difference is that it always targets external and core modules, and its policy allow/disallow values match the module source, internalPath, and import specifiers. Read Policies first, then the options below for the differences.
Rule Details
This rule validates dependencies to external and core modules. It allows or disallows them based on the element importing the module and the provided configuration.
Options
"boundaries/external":
[<severity>, { "default": <string>, "message": <string>, "rules": <object> }]
The first item in the array is the ESLint severity (0 = off, 1 = warning, 2 = error). The second item is the options object:
default:"allow"or"disallow". Determines the default behavior for external imports that don't match any policy.message: Custom error message for policy violations. Note that the default message provides detailed information about why the error occurred, so only define a custom message if necessary. See error messages for more informationrules: An array of policy objects processed in order to determine whether an import should be allowed. Each policy object contains:from:<entity selectors>- If the file being analyzed matches this selector, the policy is evaluated. Otherwise, it is skipped. Accepts both legacy element selectors (for example{ type: "helper" }) and entity selectors (for example{ element: { type: "helper" } })disallow:<external module selectors>- If the imported external module matches this selector, the import is disallowed (can be overridden by a subsequent policy returning"allow")allow:<external module selectors>- If the imported external module matches this selector, the import is allowed (can be overridden by a subsequent policy returning"disallow")importKind:<string>- Optional. Makes sense when using TypeScript only. If defined, the policy is only evaluated for dependencies of the specified kind. Possible values:"value","type", or"typeof"message:<string>- Custom error message for this specific policy. See error messages for more information
Each policy requires from and at least one of allow or disallow.
The policy-level importKind option is deprecated. When you migrate to boundaries/dependencies, replace it with the dependency.kind selector property. See the v6 to v7 migration guide for details.
External Module Selectors
The allow and disallow properties support one or multiple patterns to match external libraries. Each pattern can have the following formats:
<string>- A micromatch pattern to match the module name[<string>, <object>]- An array containing a micromatch pattern as the first element and an options object with the following properties:specifiers:<array>- Array of import specifiers to match. Each specifier can be expressed as a micromatch pattern.path:<string>or<array<string>>- Micromatch patterns to match the imported subpath from the module. If an array is provided, the module matches if any pattern matches. Note that paths must start with a/character.
When using options:
- If
pathis provided, the pattern only matches if the imported subpath from the module matches any of the patterns - If
specifiersis provided, the pattern only matches if any of the specifiers is used in the import statement
Pattern Matching Examples
| Pattern | Import Statement | Match |
|---|---|---|
"foo-library" | import "foo-library" | ✅ |
"foo-library" | import { Link } from "foo-library" | ✅ |
"foo-library" | import { Link, Foo } from "foo-library" | ✅ |
"foo-library" | import "another-library" | ❌ |
["foo-library", { specifiers: ["Link"] }] | import { Link } from "foo-library" | ✅ |
["foo-library", { specifiers: ["Link"] }] | import { Link, Foo } from "foo-library" | ✅ |
["foo-library", { specifiers: ["Link"] }] | import "foo-library" | ❌ |
["foo-library", { specifiers: ["Link"] }] | import { Foo } from "foo-library" | ❌ |
["foo-library", { specifiers: ["Link"] }] | import "another-library" | ❌ |
["foo-*", { specifiers: ["L*", "F*"] }] | import { Link } from "foo-library" | ✅ |
["foo-*", { specifiers: ["L*", "F*"] }] | import { Foo } from "foo-another-library" | ✅ |
["foo-*", { specifiers: ["L*", "F*"] }] | import { Var, Foo } from "foo-library" | ✅ |
["foo-*", { specifiers: ["L*", "F*"] }] | import "foo-library" | ❌ |
["foo-*", { specifiers: ["L*", "F*"] }] | import "another-library" | ❌ |
["foo-library", { path: "/subpath" }] | import "foo-library/subpath" | ✅ |
["foo-library", { path: "/utils/*" }] | import "foo-library/utils/helper" | ✅ |
["foo-library", { path: "/utils/*" }] | import "foo-library/utils" | ❌ |
["foo-library", { path: ["/subpath", "/utils/*"] }] | import "foo-library/another" | ❌ |
Configuration Example
{
rules: {
"boundaries/external": [2, {
// disallow all external imports by default
default: "disallow",
policies: [
{
// from helper elements
from: { type: "helper" },
// allow importing moment
allow: ["moment"],
// allow only importing types, not values (TypeScript only)
// note: importKind is deprecated, use dependency.kind after migrating
importKind: "type"
},
{
// from component elements
from: { type: "component" },
allow: [
// allow importing react
"react",
// allow importing any @material-ui module
"@material-ui/*"
]
},
{
// from components of family "molecules"
from: { type: "component", captured: { family: "molecules" } },
disallow: [
// disallow importing @material-ui/icons
"@material-ui/icons"
]
},
{
// from modules
from: { type: "module" },
allow: [
// allow importing react
"react",
// allow importing useHistory, Switch and Route from react-router-dom
["react-router-dom", { specifiers: ["useHistory", "Switch", "Route"] }],
// allow importing Menu icon and any icon starting with "Log" from @mui/icons-material
["@mui/icons-material", { path: ["/Menu", "/Log*"] }]
]
}
]
}]
}
}
Settings
The following examples use this project structure and settings configuration.
Project structure:
src/
├── components/
│ ├── atoms/
│ │ ├── atom-a/
│ │ │ ├── index.js
│ │ │ └── AtomA.js
│ │ └── atom-b/
│ │ ├── index.js
│ │ └── AtomB.js
│ └── molecules/
│ ├── molecule-a/
│ │ ├── index.js
│ │ └── MoleculeA.js
│ └── molecule-b/
│ ├── index.js
│ └── MoleculeB.js
├── helpers/
│ ├── data/
│ │ ├── sort.js
│ │ └── parse.js
│ └── permissions/
│ └── roles.js
└── modules/
├── module-a/
│ ├── index.js
│ └── ModuleA.js
└── module-b/
├── index.js
└── ModuleB.js
Settings configuration:
{
settings: {
"boundaries/elements": [
{
type: "helper",
pattern: "helpers/*",
capture: ["family"]
},
{
type: "component",
pattern: "components/*/*",
capture: ["family", "elementName"]
},
{
type: "module",
pattern: "modules/*",
capture: ["elementName"]
}
]
}
}
Examples
Incorrect
Helpers importing value from moment:
// src/helpers/data/parse.js
import moment from 'moment'
Helpers importing react:
// src/helpers/data/parse.js
import React from 'react'
Components importing moment:
// src/components/atoms/atom-a/AtomA.js
import moment from 'moment'
Molecule components importing @material-ui/icons:
// src/components/molecules/molecule-a/MoleculeA.js
import { Info } from '@material-ui/icons'
Modules importing withRouter from react-router-dom:
// src/modules/module-a/ModuleA.js
import { withRouter } from 'react-router-dom'
Modules importing non-allowed icons from @mui/icons-material:
// src/modules/module-a/ModuleA.js
import { Home } from '@mui/icons-material'
Correct
Helpers importing type from moment:
// src/helpers/data/parse.js
import type moment from 'moment'
Components importing react:
// src/components/atoms/atom-a/AtomA.js
import React from 'react'
Components importing @material-ui/core:
// src/components/atoms/atom-a/AtomA.js
import { Button } from '@material-ui/core'
Modules importing react:
// src/modules/module-a/ModuleA.js
import React from 'react'
Modules importing useHistory from react-router-dom:
// src/modules/module-a/ModuleA.js
import { useHistory } from 'react-router-dom'
Modules importing Menu icon from @mui/icons-material:
// src/modules/module-a/ModuleA.js
import Menu from '@mui/icons-material/Menu'
Modules importing Login icon from @mui/icons-material:
// src/modules/module-a/ModuleA.js
import Login from '@mui/icons-material/Login'
Error Messages
This rule provides detailed error messages to help you understand and resolve violations.
It uses the same default message format as boundaries/dependencies. Messages include information about the external dependency that is not allowed, such as the module origin and source and, when applicable, the import specifiers and internal path details (read Classification for more information on how files and modules are described).
For example:
Dependencies with module source "react" to entities of module with origin "external" are not allowed in elements of type "helper". Denied by rule at index 0
Custom Messages with Templates
You can customize error messages globally or for specific rules. See Rules Configuration -> Message Templating for more details.
report in Legacy Custom Messages
This rule populates the report object with rule-specific metadata, but only when using legacy message templates syntax. The following properties are available in the report object for legacy templates:
${report.specifiers}: Comma-separated matching import/export specifiers, when the violation is about specifiers. Modern templates should use{{dependency.specifiers}}instead.${report.path}: Matching internal import path, when the violation is about a path rule. Modern templates should use{{to.module.internalPath}}instead (the subpath inside the external module).
When neither condition applies, report contains no properties.
Migration to boundaries/dependencies
The restrictions enforced by this rule can also be achieved with the boundaries/dependencies rule, which lets you match external dependencies directly using the to.module sub-selector properties (origin, source, and internalPath) together with the dependency.specifiers metadata selector. The module layer explains how the plugin classifies each import's origin and source. It is recommended to migrate your configuration to boundaries/dependencies as soon as possible, as this rule will be removed in a future major version.
You need to set the checkAllOrigins option to true in your boundaries/dependencies configuration to make sure that dependencies from external origins are also checked, as by default boundaries/dependencies only checks dependencies between local known elements.
The following example shows how to migrate a configuration from boundaries/external to boundaries/dependencies:
{rules: {"boundaries/external": [2, {"boundaries/dependencies": [2, {checkAllOrigins: true,default: "disallow",policies: [{from: "helper",allow: ["moment"],importKind: "type"},{from: { element: { type: "helper" } },allow: {to: {module: {origin: ["external", "core"],source: "moment"}},dependency: { kind: "type" }}},{from: "helper",allow: [["@mui/material", {path: "/Autocomplete",specifiers: ["useAutocomplete"]}]]}{from: { element: { type: "helper" } },allow: {to: {module: {origin: ["external", "core"],source: "@mui/material",internalPath: "Autocomplete"}},dependency: { specifiers: ["useAutocomplete"] }}}]}]}}
Further Reading
Learn more about related topics:
- Defining Elements - Learn how to define architectural elements in your project
- Selectors - Learn how to use element, file, and module selectors (the
to.modulesub-selector matches external dependencies) - Policies - Learn how to configure common rule options
- Global Settings - Learn about global settings that affect all rules
- Migrating from v6 to v7 - Migrate deprecated rules to
boundaries/dependencies