avoid_banned_imports
Flags imports of libraries you ban, optionally only inside the directories you name. This is architecture enforcement: it keeps a Clean Architecture domain layer free of Flutter, stops dart:io reaching code that also targets the web, and confines a legacy package to the module still using it.
Layering rules are the easiest architectural decision to make and the hardest to keep. A domain layer stays platform-independent only as long as nobody types import 'package:flutter/material.dart' — and code review catches that in the pull request it appears in, not in the fifty that follow.
This rule is in no preset and reports nothing until you add banned:. Every example below shows the configuration that produces it.
many_lints: rules: avoid_banned_imports: banned: - deny: ['package:flutter/material.dart'] in: ['lib/domain/**'] message: 'The domain layer must not depend on Flutter.'rules: avoid_banned_imports: banned: - deny: ['package:flutter/material.dart'] in: ['lib/domain/**'] message: 'The domain layer must not depend on Flutter.'Write the URI exactly as it appears in the import line, not as a file path. in: takes globs relative to the package root; omit it to ban everywhere.
Examples
Section titled “Examples”Keep Flutter out of the domain layer
Section titled “Keep Flutter out of the domain layer”- deny: ['package:flutter/material.dart', 'package:flutter/widgets.dart'] in: ['lib/domain/**'] message: 'The domain layer must not depend on Flutter.'// Don't — in lib/domain/cart.dartimport 'package:flutter/material.dart'; // LINT
Color badgeColor(int items) => items > 0 ? Colors.red : Colors.grey;// Do — in lib/domain/cart.dart: return a value the UI layer maps to a Colorenum CartBadge { empty, filled }
CartBadge badgeFor(int items) => items > 0 ? CartBadge.filled : CartBadge.empty;The same import inside lib/ui/** is untouched — only the directories named in
in: are checked.
Keep dart:io out of code that also targets the web
Section titled “Keep dart:io out of code that also targets the web”- deny: ['dart:io'] in: ['lib/shared/**'] message: 'Shared code also runs on the web; use an injected abstraction.'// Don't — in lib/shared/config_loader.dartimport 'dart:io'; // LINT
Future<String> readConfig(File file) => file.readAsString();// Do — in lib/shared/config_loader.dartabstract class ConfigSource { Future<String> read();}
class ConfigLoader { const ConfigLoader(this._source);
final ConfigSource _source;
Future<String> load() => _source.read();}Retire a package across the whole repo
Section titled “Retire a package across the whole repo”deny_pattern is anchored to the whole URI, so package:legacy_.* matches
every library in every package whose name starts with legacy_:
- deny_pattern: ['package:legacy_.*'] message: 'Legacy packages are being removed; see ADR-014.'// Don'timport 'package:legacy_http/legacy_http.dart'; // LINT
// Doimport 'package:http/http.dart';Force one entry point for a package
Section titled “Force one entry point for a package”Ban the deep paths and leave the barrel, so consumers depend on the public API rather than internals:
- deny_pattern: ['package:design_system/src/.*'] message: 'Import package:design_system/design_system.dart instead.'// Don'timport 'package:design_system/src/buttons/primary_button.dart'; // LINT
// Doimport 'package:design_system/design_system.dart';Ban one library everywhere except its owner
Section titled “Ban one library everywhere except its owner”in: scopes an entry; the rule-wide exclude: skips paths for the whole rule.
They compose into “banned across the app, except in the module that owns it”:
many_lints: rules: avoid_banned_imports: exclude: ['lib/infrastructure/analytics/**'] banned: - deny: ['package:firebase_analytics/firebase_analytics.dart'] in: ['lib/**'] message: 'Log through AnalyticsService, not the vendor SDK.'rules: avoid_banned_imports: exclude: ['lib/infrastructure/analytics/**'] banned: - deny: ['package:firebase_analytics/firebase_analytics.dart'] in: ['lib/**'] message: 'Log through AnalyticsService, not the vendor SDK.'Options
Section titled “Options”| Option | Type | Default | Description |
|---|---|---|---|
banned |
list of entries | [] |
The entries to enforce. With none, the rule reports nothing |
Per entry:
| Key | Type | Required | Description |
|---|---|---|---|
deny |
string or list | one of deny / deny_pattern |
Import URIs banned by exact match |
deny_pattern |
string or list | one of deny / deny_pattern |
Regular expressions, anchored to the whole URI |
in |
list of globs | no | Paths, relative to the package root, where the entry applies. Omit to apply everywhere |
message |
string | no | A project-specific explanation appended to the diagnostic |
Known limitations
Section titled “Known limitations”The URI is matched as written. deny: ['package:flutter/material.dart'] does not also ban package:flutter/widgets.dart, and a relative import is matched as the relative path you typed. List each URI, or use deny_pattern.
Exact unless you ask otherwise. Banning async does not ban dart:async — deny never matches a substring. deny_pattern opts into matching by shape and anchors to the whole URI, so dart:.* catches every dart: library while legacy matches only a library named exactly legacy.
Transitive imports are invisible. A file importing a permitted library that itself imports a banned one is not reported; the rule reads the import lines in the file it is analysing.
A malformed entry is skipped silently. An entry denying nothing is dropped and an invalid regular expression costs you that pattern only — a plugin cannot report problems against a YAML file, so bad configuration degrades quietly rather than failing analysis.
Related rules
Section titled “Related rules”avoid_banned_annotations— Ban specific annotations, optionally scoped by directory.avoid_banned_exports— Ban re-exports of specific libraries, optionally scoped by directory.avoid_banned_names— Ban specific identifiers from being used as declaration names.avoid_banned_types— Ban specific types from being named, optionally scoped by directory.