Policies
Policies are how you define which dependencies are allowed or disallowed for the boundaries/dependencies rule. Each policy uses dependency selectors to match specific dependencies and decide whether they pass, giving you a high level of customization for your architectural boundaries.
Rule Format
boundaries/dependencies is the canonical rule for restricting dependencies between elements. This page documents how to configure it: you set a default effect (allow or disallow), then provide an array of policies that override that default.
The deprecated rules boundaries/element-types, boundaries/external, and boundaries/entry-point build on this same base configuration, with their own differences. Their pages document those differences and how to migrate to boundaries/dependencies.
Each matching policy overrides previous values, so the final result is determined by the last matching policy.
Basic structure:
export default [{
rules: {
"boundaries/dependencies": [2, {
// Default effect when no policy matches
default: "allow",
// Optional custom message (overridable per policy)
message: "{{from.element.types}} is not allowed to depend on {{to.element.types}}",
// Array of policies
policies: [
{
from: { element: { type: "helper" } },
// Effect when the dependency matches this policy
disallow: {
to: { element: { type: ["module", "component"] } }
},
// Optional custom message for this policy (overrides the top-level message)
message: "Helpers must not import modules or components"
},
{
dependency: { kind: "type" },
// Effect when the dependency matches this policy
disallow: { from: { element: { type: "helper" } } },
// Optional custom message for this policy (overrides the top-level message)
message: "Helpers cannot import types"
}
]
}]
}
}]
- All policies are evaluated, and the final result comes from the last matching policy.
- If a policy has both
allowanddisallowproperties and both match,disallowtakes priority. - Each policy resolves to
"allow"or"disallow", producing an ESLint error when the result is"disallow".
Policy Properties
from / to
Type: entity selector { element?, file?, module? } (or an array of them for OR), with backward-compatible flat element selectors also accepted.
Determines when the policy applies:
from- The policy applies when the file being analyzed matches this selector.to- The policy applies when the imported dependency matches this selector.
An entity selector describes one file along three independent dimensions, each optional:
element- the architectural element the file belongs to, categorized by element descriptors (type,types,captured,path,fileInternalPath,parent...). See Element Selectors.file- the file itself, categorized by file descriptors (categories,captured,path). See File Selectors.module- the resolved module of the dependency (origin,source,internalPath). This is the dimension to use for external dependencies.
A selector matches only when every sub-selector you provide matches (an omitted sub-selector matches anything).
// Files inside "helper" elements
{
from: {
element: { type: "helper" }
}
}
// Test files inside "component" elements (element AND file must match)
{
from: {
element: { type: "component" },
file: { categories: "test" }
}
}
// External React imports (matched on the module dimension)
{
to: {
module: { origin: "external", source: "react" }
}
}
// Dependencies that are components of family "atoms" OR any test file
{
to: [
{ element: { type: "component", captured: { family: "atoms" } } },
{ file: { categories: "test" } }
]
}
Legacy element selectors (for example from: { type: "helper" }) are still supported for backward compatibility, but they are deprecated. Read Legacy Element Selectors and the v6 to v7 migration guide for more information and migration instructions.
The target property is a deprecated alias for to. It still works, but use to for clarity. Read Legacy Policy Configuration for examples and migration.
Read the "How policies match dependencies" section below for a detailed explanation of how from, to, and dependency properties are combined to determine if a policy matches a specific dependency.
dependency
Type: <dependency metadata selector> (see Dependency Selectors documentation)
This property matches dependencies by their metadata (for example import kind or relationship type). Combine it with from and to to create more specific rules.
// Rule applies to dependencies that are type imports (e.g. TypeScript type imports)
// to "helper" elements, regardless of the type of the importing file
{
dependency: { kind: "type" },
to: { element: { type: "helper" } },
allow: { from: { element: { type: "*" } } },
}
The dependency.module selector is kept for backward compatibility but is deprecated. Use to.module.source via the entity selector instead — for example to: { module: { origin: "core", source: "react" } }. Read Legacy Dependency Selectors and the v6 to v7 migration guide for more information and migration.
Read the "How policies match dependencies" section below for a detailed explanation of how from, to, and dependency properties are combined to determine if a policy matches a specific dependency.
allow / disallow
Type: <dependency selector> or <array of dependency selectors>
These properties determine the effect of the policy when it matches a dependency. Each policy must have either an allow or disallow property (or both, in which case disallow takes precedence).
// Components can import helpers and other components
{
from: { element: { types: "component" } },
allow: { to: { element: { types: ["helper", "component"] } } }
}
// Helpers cannot import anything other than other helpers
{
from: { element: { types: "helper" } },
disallow: { to: { element: { types: "!helper" } } }
}
// Any file can import other files of the same element (internal dependencies)
{
allow: { dependency: { relationship: { to: "internal" } } }
}
// Any file can import helpers, but only as a type dependency (e.g. TypeScript type imports)
{
allow: {
to: { element: { types: "helper" } },
dependency: { kind: "type" }
}
}
If both allow and disallow are present and match, disallow takes precedence.
Read the "How policies match dependencies" section below for a detailed explanation of how from, to, and dependency properties are combined to determine if a policy matches a specific dependency.
message
Type: <string>
Optional
Custom error messages let you express more helpful feedback tailored to your project conventions, in addition to the rule's built-in default message.
The rule provides a detailed default message. You can override it by setting message in the options.
You can define a custom message at the top level of the rule, which applies to every policy in the policies array, or on an individual policy for more specific messages.
Message Templating
Custom error messages use Handlebars templates to insert information about the dependency violation. The data tree mirrors the runtime descriptions of the dependency: each side (from and to) exposes the element, file, and module sub-objects.
Example:
// When a "component" element imports a file categorized as "test", the message becomes:
// "component cannot import files categorized as: test"
{
"message": "{{ from.element.types.[0] }} cannot import files categorized as: {{ to.file.categories }}"
}
Template Context
The Handlebars context for custom messages has these top-level variables:
from- The entity importing the dependency.to- The entity being imported.dependency- Metadata about the dependency itself.policy- The policy that produced the error, ornullwhen the error comes from the default policy (no policy matched). (ruleis a deprecated alias, see Legacy Policy Configuration.)
Both from and to carry the full entity data tree. These are the canonical paths:
| Variable | Type | Notes |
|---|---|---|
{{from.element.types}} | array | All matched element types |
{{from.element.types.[0]}} | string | First element type |
{{from.element.captured.X}} | string | Captured value X from the from element |
{{from.element.path}} | string | Element path |
{{from.element.fileInternalPath}} | string | File path relative to its element |
{{from.element.parents.[0].types.[0]}} | string | First parent's first type |
{{from.file.categories}} | array | File categories from file descriptors |
{{from.file.captured.X}} | string | Captured value X from the from file descriptors |
{{from.module.origin}} | string | "local", "external", or "core" |
{{from.module.source}} | string | Base module/package name (e.g. "react"), null for local |
{{from.module.internalPath}} | string | Sub-path inside an external/core module |
{{to.*}} | — | Same structure as from, for the imported entity |
{{dependency.kind}} | string | "value", "type", or "typeof" |
{{dependency.source}} | string | Literal import/export source string |
{{dependency.specifiers}} | array | Imported/exported specifier names |
{{dependency.relationship.from}} / {{dependency.relationship.to}} | string | Relationship between the two elements |
{{policy.index}} | number | Index of the matching policy (null for default policy) |
{{policy.selector}} | object | The full selector (from, to, dependency) of the matching policy — any selector field is available, e.g. {{policy.selector.from.element.type}}, {{policy.selector.to.file.categories}}, {{policy.selector.dependency.kind}} |
Use Handlebars bracket notation to read array items, for example {{from.element.types.[0]}} for the first type.
For the complete property reference of from, to, and dependency, see Classification and its per-layer tables (element, file, module). policy.selector is the full selector object of the matching policy (from, to, dependency, each of which can contain element, file, and module sub-selectors) — see Selectors → Dependency Selectors for its complete shape.
Usage in templates:
{
// Access importing/imported element types
"message": "Elements of type {{from.element.types}} cannot import type {{to.element.types}}",
// Access captured values
"message": "Module {{from.element.captured.elementName}} cannot import {{to.element.captured.family}} components",
// Access file categories
"message": "{{from.element.types}} cannot import {{to.file.categories}} files",
// Access the resolved module of the dependency
"message": "Importing {{to.module.source}} ({{to.module.origin}}) is not allowed here",
// Access dependency information
"message": "Cannot import {{dependency.kind}} from {{to.element.types}}",
// Access policy information
"message": "Denied by policy at index {{policy.index}} (from {{policy.selector.from.element.type}} to {{policy.selector.to.element.type}})"
}
Missing properties in Handlebars templates resolve to an empty string.
For backward compatibility, each from/to side also exposes flat V6 fields in custom messages (for example {{from.type}} instead of {{from.element.types.[0]}}), and the legacy ${...} message syntax still works. Both are deprecated — see Legacy Policy Configuration.
importKind (deprecated)
Policy-level importKind is kept for backward compatibility but is deprecated. Use the dependency.kind metadata selector instead; when both are defined, dependency.kind takes precedence. See Legacy Policy Configuration for examples and migration.
Policies Evaluation Order
Policies are evaluated sequentially, and each matching policy can override previous results:
{
default: "disallow",
policies: [
// If this matches: result is "allow"
// Components can import helpers
{
from: { element: { types: "component" } },
allow: { to: { element: { types: "helper" } } }
},
// If this also matches: result is "disallow" (overrides previous).
// Component family "atoms" cannot import "data" helpers
{
from: { element: { types: "component", captured: { family: "atoms" } } },
disallow: { to: { element: { types: "helper", captured: { family: "data" } } } },
},
// If this also matches: result is "allow" (overrides previous).
// Component family "atoms" can import the "data/sort" helper, in exception to previous policy
{
from: { element: { types: "component", captured: { family: "atoms" } } },
allow: {
to: { element: { types: "helper", captured: { family: "data" }, fileInternalPath: "sort.js" } }
}
}
]
}
Policy order matters: Place more specific policies after more general policies so they can override when needed.
How policies match dependencies
Policies match dependencies based on the properties of the importing file, the imported file, and the dependency itself. These properties are available in the runtime description of each entity and dependency.
Combining selectors
The from, to, and dependency selectors in your policies are combined with the properties of the allow and disallow effects to build an unique dependency selector that determines if the policy matches a specific dependency. For example, if you have a policy like this:
{
from: { element: { type: "component" } },
allow: {
to: { element: { type: "helper" } },
dependency: { kind: "value" }
}
}
This policy allows a dependency when all of the following conditions are met:
- The importing file is described as a "component" element
- The imported file is described as a "helper" element
- The dependency is a value import (not a type import)
You can combine from, to, and dependency properties both at the policy level and inside allow/disallow to create more specific selectors. For example:
{
from: { element: { type: "component" } },
dependency: { kind: "value" },
disallow: {
to: [
{ element: { types: ["helper", "module"] } },
{ file: { categories: "test" } }
],
},
}
The same policy can be expressed in different ways. Where to define these properties depends on the specific policy you want to create, how detailed you want it to be, and it is also a matter of preference. The important thing is to understand that all these properties are combined to determine if a policy matches a specific dependency.
The plugin is very flexible and allows you to define policies in the way that makes more sense for each specific case.
Merging OR conditions (arrays)
When using arrays to define OR conditions, the properties are merged together for each item in the arrays.
For example, the next policy applies to dependencies whose importing file is either:
- a "component" element with a parent of type "module", or
- a test file (
file.categoriesincludes "test") with a parent of type "module".
{
from: [
{ element: { type: "component" } },
{ file: { categories: "test" } }
],
allow: {
from: {
element: {
parent: { type: "module" }
}
},
dependency: {
relationship: { to: "parent" }
}
},
}
Using the same property at policy level and in effect level
When the same property is defined both at the policy level and inside allow/disallow, they are merged together to create the final selector, giving precedence to the properties defined inside allow/disallow. For example, in the next policy:
allowonly applies to dependencies from "component" elements withelementName"component-a", to any helper.disallowonly applies to dependencies from "component" elements withelementName"component-b", to helpers withelementName"helper-a".
{
from: { element: { type: "component" } },
allow: {
from: { element: { captured: { elementName: "component-a" } } },
to: { element: { type: "helper" } },
},
disallow: {
from: { element: { captured: { elementName: "component-b" } } },
to: { element: { type: "helper", captured: { elementName: "helper-a" } } },
}
}
Merging nested properties
When using nested properties like parent, captured or relationship, the properties are merged together as well. For example, in the next policy:
allowapplies to components withelementName"component-a" andfamily"atoms", importing any helper.
{
from: {
element: { captured: { elementName: "component-a" } },
},
allow: {
to: { element: { type: "helper" } },
from: { element: { captured: { family: "atoms" } } },
},
}
Extending nested properties is not supported when using arrays (OR conditions) in the selectors.
For example, in the next policy the allow selector is not merged with the policy-level selector. The policy allows anything with family "atoms" to import any helper, because the allow policy takes precedence over the policy-level selectors and overrides the from.element.captured value instead of merging with it.
{
from: {
// Arrays in nested properties are not merged
element: { captured: [{ elementName: "component-a" }] },
},
allow: {
to: { element: { type: "helper" } },
from: { element: { captured: { family: "atoms" } } }, // Only this is applied
},
}
Complete Example
To illustrate the level of customization the plugin supports, here is a fully commented boundaries/dependencies rule that uses entity selectors, file categories, and module matching in policies. It assumes the canonical demo setup:
settings: {
"boundaries/elements": [
{ type: "helper", pattern: "helpers/*", capture: ["family"] },
{ type: "component", pattern: "components/*/*", capture: ["family", "elementName"] },
{ type: "module", pattern: "modules/*", capture: ["elementName"] }
],
"boundaries/files": [
{ pattern: "**/*.spec.js", category: "test" },
{ pattern: "**/*.css", category: "style" }
]
}
export default [{
rules: {
"boundaries/dependencies": [2, {
// Disallow every dependency by default
default: "disallow",
policies: [
{
// Helpers can import other helpers
from: { element: { type: "helper" } },
allow: { to: { element: { type: "helper" } } }
},
{
// Components can import...
from: { element: { type: "component" } },
allow: {
to: [
// ...components of the same family (templated from the importing element)...
{ element: { type: "component", captured: { family: "{{from.element.captured.family}}" } } },
// ...and helpers of the "data" family
{ element: { type: "helper", captured: { family: "data" } } }
]
}
},
{
// Components of family "molecules" can import components of family "atoms"
from: { element: { type: "component", captured: { family: "molecules" } } },
allow: {
to: { element: { type: "component", captured: { family: "atoms" } } },
},
},
{
// Components of family "atoms" cannot import "data" helpers
from: { element: { type: "component", captured: { family: "atoms" } } },
disallow: {
to: { element: { type: "helper", captured: { family: "data" } } }
},
// Custom message only for this specific error
message: "Atom components can't import data helpers"
},
{
// No file may import a test file
disallow: { to: { file: { categories: "test" } } },
message: "Do not import test files ({{to.file.categories}}) from production code"
},
{
// Modules can import any component or helper
from: { element: { type: "module" } },
allow: {
to: { element: { type: ["helper", "component"] } }
}
},
{
// Modules cannot import external state libraries directly
from: { element: { type: "module" } },
disallow: {
to: { module: { origin: "external", source: "redux" } }
},
message: "Modules must not import {{to.module.source}} directly"
},
{
// Modules whose name starts with "page-" can only import layout components
from: { element: { type: "module", captured: { elementName: "page-*" } } },
disallow: {
to: { element: { type: "component", captured: { family: "!layout" } } },
},
// Custom message only for this specific error
message: `
Modules with a name starting by 'page-' can only import layout components.
You tried to import a component of family {{to.element.captured.family}}
from a module named {{from.element.captured.elementName}}
`
}
]
}]
}
}]
Next Steps
- Legacy - the legacy policy configuration reference, with migration instructions.
- Selectors - the full reference for element, file, module, and entity selectors used in
from/to. - Settings - configure
boundaries/files,boundaries/elements-single-type, and the other plugin settings. boundaries/dependenciesrule - the rule reference, with project structure and example diagnostics.- v6 to v7 migration guide - migrate legacy selectors, templates, and deprecated rules.