Skip to main content
Boundaries enforce import direction between architectural zones. A zone groups files by glob pattern; a rule declares which zones may import from which. Violations appear as boundary-violation issues in fallow dead-code output. For preset descriptions, output formats, and suppression, see Architecture boundaries.

Presets

Fallow ships four built-in presets for common architecture patterns:
PresetZonesDescription
layered4Classic N-tier: presentation, application, domain, infrastructure
hexagonal3Ports and adapters: adapters, ports, domain
feature-sliced6Feature-Sliced Design: strict downward-only imports
bulletproof4Bulletproof React: app, features, shared, server
{
  "boundaries": {
    "preset": "bulletproof"
  }
}
See Architecture boundaries - Presets for the full zone and rule definitions of each preset.

tsconfig rootDir detection

When a preset is active, fallow reads compilerOptions.rootDir from tsconfig.json to determine the source root for zone patterns. Preset zones become {rootDir}/{zone}/**. The detection logic:
  1. Reads tsconfig.json from the project root (supports JSONC with comments and trailing commas)
  2. Extracts compilerOptions.rootDir
  3. Strips a leading ./ prefix and any trailing /
  4. Rejects ., .., and absolute paths for security
  5. Falls back to src if the field is missing, the file doesn’t exist, or the value is rejected
tsconfig.json
{
  "compilerOptions": {
    "rootDir": "./lib"
  }
}
With the hexagonal preset, this produces zones:
lib/adapters/**
lib/ports/**
lib/domain/**
rootDir detection only applies to presets. Custom zones use patterns exactly as written.

Custom zones

Define zones manually when presets don’t match your architecture. Each zone has:
FieldTypeDescription
namestringZone identifier used in rules
patternsstring[]Glob patterns relative to the project root
rootstring?Reserved for future use (currently ignored)
{
  "boundaries": {
    "zones": [
      { "name": "ui", "patterns": ["src/components/**", "src/pages/**"] },
      { "name": "data", "patterns": ["src/db/**", "src/api/**"] },
      { "name": "shared", "patterns": ["src/lib/**", "src/utils/**"] }
    ]
  }
}
Zone classification rules:
  • A file belongs to the first zone whose pattern matches (first-match wins)
  • Files that don’t match any zone are unrestricted — they can import from and be imported by any zone
  • Zone order matters: place more specific patterns before broader ones
Fallow warns when a zone matches zero files. This usually means the glob pattern doesn’t match your directory structure. Run fallow list --boundaries to check file counts per zone.

Custom rules

Rules define which zones may import from which. Each rule has:
FieldTypeDescription
fromstringThe importing zone
allowstring[]Zones that from may import from
{
  "boundaries": {
    "zones": [
      { "name": "ui", "patterns": ["src/components/**", "src/pages/**"] },
      { "name": "data", "patterns": ["src/db/**", "src/api/**"] },
      { "name": "shared", "patterns": ["src/lib/**", "src/utils/**"] }
    ],
    "rules": [
      { "from": "ui", "allow": ["shared"] },
      { "from": "data", "allow": ["shared"] },
      { "from": "shared", "allow": [] }
    ]
  }
}
Rule semantics:
  • Self-imports are always allowed — files within the same zone can freely import each other
  • No rule entry means the zone is unrestricted — it can import from any zone
  • Empty allow list means the zone is isolated — no cross-zone imports permitted
  • Only zones with an explicit rule entry are constrained
Start with rules for your lowest-level zones (the ones that should import from nothing) and work upward.

Merging presets with custom config

When both preset and zones/rules are specified, fallow merges them:
  • Zones with the same name as a preset zone replace the preset zone entirely
  • Rules with the same from as a preset rule replace the preset rule
  • New zones and rules are added on top of the preset

Example: override a preset zone

Use the hexagonal preset but put your domain code in src/core instead of src/domain:
{
  "boundaries": {
    "preset": "hexagonal",
    "zones": [
      { "name": "domain", "patterns": ["src/core/**"] }
    ]
  }
}
This keeps adapters and ports from the hexagonal preset but replaces the domain zone pattern with src/core/**. All three rules remain unchanged.

Example: add a zone to a preset

Add a test zone to the bulletproof preset so test utilities are isolated:
{
  "boundaries": {
    "preset": "bulletproof",
    "zones": [
      { "name": "test", "patterns": ["src/__tests__/**", "src/**/*.test.ts"] }
    ],
    "rules": [
      { "from": "test", "allow": ["features", "shared", "server"] }
    ]
  }
}
The four bulletproof zones and their rules are preserved. The test zone and its rule are added on top.

Inspecting boundaries

Use fallow list --boundaries to see the expanded configuration after preset expansion and merging. This is the fastest way to verify your config is correct.
$ fallow list --boundaries
Boundaries: 4 zones, 4 rules

Zones:
  app                  3 files  src/app/**
  features             12 files src/features/**
  shared               8 files  src/components/**, src/hooks/**, src/lib/**, ...
  server               4 files  src/server/**

Rules:
  app features, shared, server
  features shared, server
  server shared
  shared               (isolated  no imports allowed)
For scripting or MCP tools, use JSON output:
fallow list --boundaries --format json --quiet
Run fallow list --boundaries first when debugging boundary violations. It shows the actual zone patterns, file counts, and resolved rules — catching misconfigured globs before you analyze.

Common patterns

Preset zone patterns are flat (src/<zone>/**), which doesn’t work when packages have separate source directories. Define zones explicitly for each package:
{
  "boundaries": {
    "zones": [
      { "name": "web-ui", "patterns": ["apps/web/src/components/**", "apps/web/src/pages/**"] },
      { "name": "web-data", "patterns": ["apps/web/src/api/**", "apps/web/src/hooks/**"] },
      { "name": "shared-ui", "patterns": ["packages/ui/src/**"] },
      { "name": "shared-utils", "patterns": ["packages/utils/src/**"] }
    ],
    "rules": [
      { "from": "web-ui", "allow": ["web-data", "shared-ui", "shared-utils"] },
      { "from": "web-data", "allow": ["shared-utils"] },
      { "from": "shared-ui", "allow": ["shared-utils"] },
      { "from": "shared-utils", "allow": [] }
    ]
  }
}
In tRPC apps, the server router typically imports from feature modules to compose sub-routers. With the bulletproof preset, this causes server -> features violations.The cleanest approach is to suppress the router composition file:
src/server/_app.ts
// fallow-ignore-file boundary-violation
import { authRouter } from '../features/auth/router'
import { billingRouter } from '../features/billing/router'

export const appRouter = router({
  auth: authRouter,
  billing: billingRouter,
})
Alternatively, add features to the server rule:
{
  "boundaries": {
    "preset": "bulletproof",
    "rules": [
      { "from": "server", "allow": ["shared", "features"] }
    ]
  }
}
Widening the server rule allows all server files to import from features, not just the router. Prefer inline suppression for surgical exceptions.
Test files often need to import from any zone. Add a test zone that sits above the preset layers:
{
  "boundaries": {
    "preset": "bulletproof",
    "zones": [
      { "name": "test", "patterns": ["src/**/*.test.ts", "src/**/*.spec.ts", "src/__tests__/**"] }
    ],
    "rules": [
      { "from": "test", "allow": ["app", "features", "shared", "server"] }
    ]
  }
}
Since zones use first-match classification, place more specific patterns (like test file globs) before broader zone patterns. When using a preset with custom zones, custom zones are appended after preset zones. If your test files live inside zone directories (e.g., src/features/auth/auth.test.ts), the preset’s features zone pattern will match first. Move the test patterns into an explicit zone list that comes before the preset zones, or use ignorePatterns to exclude test files from boundary analysis entirely.

See also

Architecture boundaries

Presets, output formats, and inline suppression for boundary violations.

Rules & Severity

Set boundary-violation to error, warn, or off.

Inline Suppression

Suppress individual findings in source code.