Config file formats
Fallow searches for config files in this order:.fallowrc.json(JSONC, comments allowed).fallowrc.jsonc(same JSONC parser, lets editors auto-detect JSON-with-comments syntax highlighting)fallow.toml.fallow.toml
.fallowrc.json and .fallowrc.jsonc are identical in behavior; the .jsonc extension only signals to editors that comments are expected. If more than one of these files coexists in the same directory, fallow loads the higher-precedence one and prints a warning on stderr naming the file it ignored, so a stale config left over from a migration cannot silently win.
Both JSON forms are parsed as JSONC, and the published schema says so (allowComments, allowTrailingCommas), so editors built on the JSON language service, such as VS Code and Zed, do not flag comments or trailing commas in a config that references schema.json.
Full example
Config fields
entry
entry
package.json (main, module, bin, exports) and framework plugins. Add files here that aren’t auto-detected.Entry point auto-detection
Entry point auto-detection
entry config, fallow discovers entry points from your package.json fields. These files are the roots of the module graph. Anything reachable from them is considered “used.”Fallow reads the following package.json fields:exports field is resolved recursively. Nested conditions like "import", "require", "types", and "default" are all followed to their target files. Subpath exports (e.g., "./utils") are included too.Output directories (dist/, build/, out/, esm/, cjs/) referenced in these fields are mapped back to src/ equivalents with source extension fallback. Fallow ignores output directories by default.On top of package.json fields, framework plugins add their own entry points (e.g., Next.js adds pages/**, app/**/page.tsx; Vitest adds **/*.test.ts). Run fallow list to see all detected entry points.ignorePatterns
ignorePatterns
**/node_modules/**, **/dist/**, **/build/**, **/coverage/** and the minified-bundle globs. Since 3.26.0 the build default is recursive like the others, so a nested build/ directory that holds hand-written source is skipped as well; ignorePatterns has no negation, so rename or move such a directory, or analyze it as its own project with fallow --root <dir>. Run with --explain-skipped to see which built-in pattern removed files and how many.ignoreFindings
ignoreFindings
ignorePatterns when you want the files out of analysis entirely./ on every platform, including Windows. Matching is case sensitive, and * and ? cross /, so src/* covers the whole src subtree. Absolute paths, .. segments, and a bare "!" are rejected at load time.Order does not matter: a negation wins over any positive match wherever it sits in the array. An array of negations alone hides every path except the ones a negation matches.Findings are hidden before rules, severities, and --fail-on-issues are applied, so a hidden finding cannot fail a run or reach any output format. A finding with several owning files (circular dependency, re-export cycle, duplicate export, prop drilling chain, and an unlisted dependency, owned by its import sites) is hidden only when every owner matches.Dependency findings owned by a manifest (unused, dev, optional, type-only, test-only, catalog, override), architecture boundary and policy violations, stale suppressions, framework correctness findings such as client/server directives and route collisions, and security candidates stay visible even when their path matches.Human output prints a stderr note naming patterns that matched no finding, which catches typos; --quiet and the machine formats leave it out.ignoreDependencies
ignoreDependencies
bun:sqlite or implicitly available dependencies.ignoreCatalogReferences
ignoreCatalogReferences
unresolved-catalog-reference findings: a catalog: dependency pointing at a catalog that does not declare the package. These findings sit in package.json rather than source files, and JSON has no comment syntax, so // fallow-ignore-next-line never reaches them and a config entry is the only suppression.package is required and matched by exact, case-sensitive string equality against the dependency key, so "@scope/*" matches nothing. catalog and consumer are optional; omitting one matches any value.catalog is matched against the normalized catalog name: "react": "catalog:" and "react": "catalog:default" are both default, and "catalog:react17" is react17. consumer is a project-root-relative glob over the consuming package.json path, with / on every platform (package.json for the root manifest, packages/app/package.json for a workspace member); matching is anchored to the whole path, and * and ? cross /. Every field of one entry must match, and any matching entry in the array suppresses the finding.Every unresolved-catalog-reference finding in fallow dead-code --format json output carries an add-to-config action with package, catalog, and consumer already filled in.Only unresolved-catalog-reference is affected; unused-catalog-entries and empty-catalog-groups are untouched. To turn the rule off for a path, use overrides with "unresolved-catalog-references": "off"; to turn it off everywhere, set rules.unresolved-catalog-references to "off". ignoreFindings does not apply here: manifest-owned findings stay visible even when their path matches. A consumer value with invalid glob syntax, an absolute path, or a .. segment fails config load.ignoreDependencyOverrides
ignoreDependencyOverrides
unused-dependency-override and misconfigured-dependency-override for matching entries in overrides (pnpm-workspace.yaml), pnpm.overrides, npm overrides, and bun resolutions. These findings sit in manifests rather than source files, so // fallow-ignore-next-line never reaches them and a config entry is the only suppression (a CVE pin that must stay, for example).package is an exact, case-sensitive string match against the override’s target package, not a glob and not the raw key: write react-dom for "react>react-dom" and @types/react for "@types/react@<18". Bun resolutions keys drop their ** path segments, so "**/trim-newlines" is matched as trim-newlines; a **/ prefix in an overrides key is not stripped. A key fallow cannot parse is matched verbatim instead, so "react>" needs { "package": "react>" }.source is the declaring file’s label, "pnpm-workspace.yaml" or "package.json", not a path; omit it to match both. Any other value loads without complaint and never matches. pnpm.overrides, npm overrides, and bun resolutions all report "package.json", so source cannot separate them. A bun project’s resolutions are read only when the root manifest declares no overrides key; alongside overrides they are shadowed and never reported, so an entry for them matches nothing. Both fields must match, and any matching entry in the array suppresses the finding.Both findings carry an add-to-config action in JSON output, with package and source already filled in. An entry that matches nothing produces no warning. To drop either finding entirely, set rules.unused-dependency-overrides or rules.misconfigured-dependency-overrides to "off". ignoreFindings does not apply here: manifest-owned findings stay visible even when their path matches.ignoreUnresolvedImports
ignoreUnresolvedImports
unresolved-import. Useful for imports fallow cannot resolve on disk: code generated later in the build, a package whose exports map points at build artifacts that are not in the checkout, or a bundler alias fallow does not model.* and ? cross /, but a trailing /** requires the separator, so list the bare package name alongside the subpath pattern when you want both. A leading ./ is stripped from each pattern, and every specifier is tried with and without its own, so "./generated/x.js" and "generated/x.js" cover the same imports.A .. segment is allowed here, unlike in the file-path glob fields, because these are specifiers rather than paths. There is no negation syntax and no ordering: a specifier is silenced if any pattern matches. A pattern with invalid glob syntax fails config load, with a message naming the field and the offending pattern.The field covers static imports, dynamic imports, and re-export sources, for both value and type-only imports. Resolution and the module graph still run as before, and dependency usage accounting is untouched, so unlisted-dependency is unaffected (that is ignoreDependencies, which matches package names by exact string).Every unresolved-import finding in JSON output carries an add-to-config action pointing at this field with the reported specifier. When moving an import here from an inline // fallow-ignore-next-line unresolved-import comment, delete the comment: the config check runs first, so the comment is then reported as stale-suppression.ignoreExports
ignoreExports
components/ui/<name>/index.ts files intentionally export the same short names.ignoreExports suppresses both unused-export findings AND duplicate-export grouping for matching files. Use exports: ["*"] to exclude every export from the matched files; use a name list to exclude only those names.overrides.rules.duplicate-exports = "off" has no effect because the rule spans multiple files (a duplicate-export finding groups N locations across N files). Fallow emits a load-time warning when this override shape is detected and points at ignoreExports as the working escape hatch.ignoreExportsUsedInFile
ignoreExportsUsedInFile
ignoreExportsUsedInFile.unused-types issue, so { "type": true }, { "interface": true }, and { "type": true, "interface": true } all behave identically: any type export referenced in the same file is suppressed. The two fields exist only for knip-config compatibility.References inside the export specifier itself (export { foo }, export { foo as bar }, export default foo) do not count as same-file uses; those exports are still reported when no other file or no other in-file expression references the binding.includeEntryExports
includeEntryExports
main/exports, framework pages, etc.) instead of auto-marking them as used. Catches typos in framework-convention exports like meatdata instead of metadata.--include-entry-exports. The CLI flag wins when both are set.typeAware
typeAware
projects is optional and accepts tsconfig paths relative to the project root.
An empty list uses automatic project discovery. Start with best-effort, which
keeps conservative findings when semantic evidence is partial or unavailable.
Choose complete only when an incomplete semantic pass must fail the run.This does not replace tsc or Oxlint. See
Type-aware TypeScript analysis for installation,
performance, safety, and library-consumer boundaries.security
security
fallow security catalogue behavior. security.categories scopes which candidate categories run, and security.requestReceivers adds project-local HTTP request object names to the built-in req, request, ctx, context, and event receiver allowlist.requestReceivers is additive only. Values are trimmed and matched case-insensitively for *.query, *.params, and *.body source reads. It does not replace the built-ins and does not gate *.searchParams.autoImports
autoImports
<Card001 /> resolving to components/Card001.vue) with no import statement.<Card /> tag is credited under includeEntryExports. Setting autoImports: true additionally drops the Nuxt component entry patterns so a genuinely-unreferenced component is reported as unused-file instead of being kept alive as an entry point.The flag is conservative: if your nuxt.config declares a components: key (custom prefix, pathPrefix, or dirs), the entry patterns are kept, since those custom layouts are not yet modeled. Defaults to false. Composable, util, and Pinia store auto-imports are tracked separately.ignoreDecorators
ignoreDecorators
@step("label"), internal labeling decorators like @measure, @log, @retry, or any custom decorator that wraps a method for logging or tracing.@step and @Inject stays skipped because @Inject is not in the ignore list. Only methods whose every decorator is in the list become subject to the standard usage check.Matching
- Bare entries (
"step"or"decorators") match the leftmost segment of the decorator path. A single bare"decorators"entry collapses every@decorators.*decorator, useful when an internal namespace exposes many utility decorators that should all be treated as non-reflective. - Dotted entries (
"decorators.log") match the full dotted path exactly. Sibling@decorators.auditdecorators stay skipped unless you also list"decorators.audit"or use the bare"decorators"form. - Both
"@step"and"step"round-trip equivalently; a leading@is stripped before matching.
Unmatched-entry warning
Entries that never match any decorator in the analyzed codebase produce a one-time warning at end of run, mirroringusedClassMembers’s warn-on-unmatched-pattern behavior. Treat unmatched entries as dead config and remove them.The default empty list preserves today’s skip-all-decorated behavior, so existing NestJS / Angular / TypeORM projects see no change. The first run after enabling this option will surface new unused-class-members findings on members previously hidden by the unconditional skip.unusedComponentProps
unusedComponentProps
unused-component-props rule. Set ignorePattern to a regex that exempts component props whose local destructure binding name matches, so a prop accepted for public-API stability but intentionally unused internally is not reported.This honors the leading-underscore “accepted-but-intentionally-unused” convention, mirroring TypeScript’s noUnusedParameters and ESLint’s @typescript-eslint/no-unused-vars varsIgnorePattern / argsIgnorePattern:ignorePattern, every unused prop is reported.Matching
- The regex is matched against the local destructure binding name (
_stageinlet { stage: _stage } = $props()), not the public prop name the finding reports (stage). When a prop is declared without an alias, the local name equals the public name. - Matching is unanchored (like ESLint’s
RegExp.test), so_with no anchor matches any prop containing an underscore (on_click,aria_label). Anchor with^_to match a leading underscore. - An invalid regex fails config load with a clear error.
unused-component-input / unused-component-output rules are not affected (their declarations are class fields, not destructured locals).usedClassMembers
usedClassMembers
agInit, refresh), TypeORM migrations (up, down), and Web Components (connectedCallback, disconnectedCallback, attributeChangedCallback).Each entry is either a plain member name (global suppression) or a scoped object that only matches classes whose heritage clause includes the configured extends or implements identifier. Strings can be exact names or glob patterns:agInit are unique enough to suppress globally. Common names like refresh or execute would produce false negatives across unrelated classes, so scope them with implements or extends. A scoped rule requires at least one of extends or implements; an unconstrained object rule ({ "members": [...] } with no heritage field) is rejected at load time.Heritage matching is syntactic: the identifier in the source’s extends Foo or implements IBar clause is compared against the rule’s string. Re-aliased imports (import { IBar as IBaz }) use whatever identifier appears in the class declaration.Glob patterns
Member strings containing* or ? are treated as glob patterns; existing exact strings keep their current meaning. "*" matches every member declared on a matching class, "enter*" matches any member whose name starts with enter, "*Handler" matches any member ending with Handler, and "on*Event" combines prefix and suffix. Useful for parser-generator listeners (ANTLR), code-generated bridges, and abstract framework bases that dispatch on a member-name prefix instead of an exhaustive list.Glob patterns that match zero members across the codebase emit a WARN at the end of the run so dead allowlist entries surface. Exact-string entries do not emit this warning (they are common boilerplate that may legitimately match in some configurations but not others).The allowlist only applies to class methods and properties. Enum members with the same names are still checked.For library-specific allowlists that should only activate when the library is installed, prefer a plugin file with the usedClassMembers field. See Custom plugins for the plugin format.publicPackages
publicPackages
dynamicallyLoaded
dynamicallyLoaded
import(), lazy routes, or plugin systems). These files are treated as entry points and will not be reported as unused.duplicates
duplicates
fallow dupes:similarCode
similarCode
fallow similar-code workflow:health
health
fallow health:audit
audit
fallow audit, the changed-file review command:fix
fix
fallow fix behavior.auto removes a leading comment block when it clearly belongs to the deleted catalog entry: the block directly follows the parent catalog: / catalogs.<name>: header, or follows a blank separator. Use always to remove every adjacent leading comment block, or never to keep leading comments in place for manual review.Two escape hatches keep curated comments safe regardless of policy:-
Add
# fallow-keepto any line in a comment block to preserve the entire block even underalways. Mirrors the existingfallow-ignore-next-line/fallow-ignore-fileinline-suppression convention: -
Under
auto, section-banner blocks are automatically preserved. A banner is any comment whose body (after#and optional whitespace) starts with three or more repeats of=,-,*,_,~,+, or#:Underalwaysthe banner heuristic does not apply; use# fallow-keepto protect banners when running withalways.
fixes[N] carries both line (1-based first deleted line, the leading comment when auto / always absorb one) and entry_line (the catalog entry’s original 1-based line). CI annotators and dedup caches that key on the entry position should use entry_line for a stable anchor; tools that want to point at the actual file edit should use line.production
production
--production, --production-dead-code, --production-health, and --production-dupes (bare combined runs and fallow audit) override config. The matching env vars FALLOW_PRODUCTION, FALLOW_PRODUCTION_DEAD_CODE, FALLOW_PRODUCTION_HEALTH, FALLOW_PRODUCTION_DUPES follow the same precedence ladder, with per-analysis env beating global env. See global flags for the full ladder.workspaces
workspaces
package.json or pnpm-workspace.yaml:extends
extends
extends field supports three source types:- Deep merge: Object fields (like
rules) are deep-merged. The child config overrides the base, but unspecified fields are inherited. - Array replacement: Array fields (like
entry,ignorePatterns) are replaced entirely, not concatenated. If the child specifiesentry, it overrides the baseentry. - Cross-format support: A JSON config can extend a TOML config and vice versa.
- Circular detection: Fallow detects circular extends chains and reports an error.
- Max depth: Extends chains are limited to 10 levels to prevent accidental deep nesting.
- String shorthand: A single path can be passed as a string instead of an array:
"extends": "./base.json".
extends field.URL extends
URL sources must usehttps:// (plain HTTP is rejected). Fallow fetches the config on every run with no caching.- Timeout: 5 seconds by default, configurable via
FALLOW_EXTENDS_TIMEOUT_SECS - Body limit: 1 MB maximum response size
- Chaining: A URL-sourced config can extend other URLs or
npm:packages, but not relative paths (there is no filesystem context to resolve against)
minimumVersion
minimumVersion
MAJOR.MINOR.PATCH.An unrecognized config key fails the run with exit 2. That is what catches an ignorePaths written where ignorePatterns was meant, but it also makes every new config field a coordinated upgrade: the day a team commits a field introduced in a newer version, every runner still on the old one breaks, on the commit that adds the field rather than on the upgrade that would explain it, with an error naming the field so the first guess is a typo.Declaring a floor turns that into a clear message.- Inherited through
extends: a monorepo base config declares it once and every config that extends it inherits the floor. - Checked before unknown keys: the version message replaces the misleading field-name error, not just precedes it.
- Gates nothing on its own: at or above the floor an unknown key still fails, because there it really is a typo.
- Optional: unset by default. Absent, nothing changes.
- Validated: a value that is not
MAJOR.MINOR.PATCH(for examplev3.21) is itself a config error.
overrides
overrides
files: Array of glob patterns matched against project-relative paths.rules: Rule severity overrides that apply to files matching the patterns.
sealed
sealed
extends. When sealed: true:extendspaths must be file-relativeextendspaths must resolve within the config’s own directory (no../escapes)npm:andhttps:extends are rejected with a clear error
- Library publishers shipping a
.fallowrc.jsonas part of an npm package can guarantee the config is self-contained - Monorepo sub-packages (e.g., a shared component library used by multiple apps) that intentionally do not inherit from the monorepo root config
sealed: true does not affect config discovery. Fallow’s first-match-wins walk already stops at the nearest config in the directory tree. This option only constrains what extends can reference.boundaries
boundaries
layered, hexagonal, feature-sliced, bulletproof. Or define custom zones and rules for full control.See Architecture boundaries for presets, custom zones, examples, and output formats.rulePacks
rulePacks
banned-call, banned-import, and banned-effect rules, loaded as pure data (no project code ever executes). Paths are project-root-relative. Matches report as policy-violation findings identified by <pack>/<rule-id>.version: 1, a unique name, and a non-empty rules array. Pack names and rule ids must use ASCII letters, digits, ., _, or - so scoped suppression comments can name them unambiguously. banned-call rules match callee paths segment-aware and import-resolved (child_process.* covers named, namespace, and default imports from child_process and node:child_process); banned-import rules match raw import specifiers segment-aware (moment covers moment/locale/nl, never moment-timezone). Rules scope with optional files / exclude globs, skip type-only imports with "ignoreTypeOnly": true, and carry an optional per-rule severity overriding the rules."policy-violation" master (default warn). Suppress one rule with // fallow-ignore-next-line policy-violation:<pack>/<rule-id> or the file-level form; bare policy-violation remains the broad family token.Run fallow rule-pack-schema to print the pack JSON Schema for editor autocomplete, or point $schema at https://raw.githubusercontent.com/fallow-rs/fallow/main/rule-pack-schema.json. Invalid or missing packs fail config load with exit code 2 instead of silently enforcing nothing. Keep pack files in a committed directory such as rule-packs/; .fallow/ is the gitignored cache directory, so packs stored there vanish from teammates’ checkouts.See Policy violations for the issue type details.rules
rules
error (default, fails CI with exit code 1), warn (reports but exits 0), or off (skip detection entirely).css-token-drift, css-duplicate-block, css-selector-complexity, css-dead-surface, and css-broken-reference. They default to warn, so they stay visible and verdict-neutral unless you set them to error or off.See Rules and severity for the full list of issue types, including pnpm catalog hygiene such as unused-catalog-entries and empty-catalog-groups, default severities, and per-glob overrides via the overrides field.flags
flags
fallow flags for the command that consumes this configuration.resolve
resolve
package.json exports and imports maps.worker, edge-light, deno, or any custom condition your bundler uses. The built-in development condition already ships in the baseline, so packages that declare a development branch (common in monorepos where development points at source and import points at compiled output) resolve to source without any config.package.json like:./api to src/api.worker.ts instead of dist/api.js.See the Node.js community conditions reference for the full list of established condition names.codeowners
codeowners
--group-by owner. When unset, fallow auto-probes CODEOWNERS, .github/CODEOWNERS, .gitlab/CODEOWNERS, and docs/CODEOWNERS. Set this to use a non-standard location.framework
framework
plugins
plugins
*.toml, *.json, *.jsonc files in .fallow/plugins/ and fallow-plugin-*.{toml,json,jsonc} in the project root.cache
cache
dir to relocate the extraction cache and maxSizeMb to cap its serialized size and trigger LRU eviction during save..fallow/cache.bin under the project root. When cache.dir is set, fallow stores the cache file under that directory. The env var FALLOW_CACHE_DIR overrides cache.dir when both are set.Default cap: 256 MB. The cap is the size of the serialized extraction cache; saves that cross 80% of the cap evict the oldest entries down to 60%. The env var FALLOW_CACHE_MAX_SIZE overrides cache.maxSizeMb when both are set. Both size values are interpreted as whole megabytes.The cache invalidates automatically when extraction-affecting config changes
(currently active plugin names and inline framework definition names).
Detection-only fields such as entry, ignorePatterns, and severity overrides
do not bust the extraction cache.--no-cache disables the cache entirely; this section is then irrelevant.regression
regression
--save-regression-baseline (no path argument), read by --fail-on-regression.JSON Schema
The$schema field enables autocomplete and validation in your editor: