Quick start
Create the plugin file
Create a Fallow auto-discovers
fallow-plugin-<name>.jsonc file in your project root:fallow-plugin-* files in your project root.Configure the fields
Customize the plugin fields for your framework. Only
name is required. Add the fields relevant to your use case.Required
| Field | Type | Description |
|---|---|---|
name | string | Unique plugin name (shown in fallow list --plugins) |
Optional
| Field | Type | Description |
|---|---|---|
enablers | string[] | Package names that activate this plugin |
entryPoints | string[] | Glob patterns for entry point files |
configPatterns | string[] | Glob patterns for config files (always-used) |
alwaysUsed | string[] | Files always considered used |
toolingDependencies | string[] | Packages used via CLI, not imports |
detection | object | Rich activation logic (see below) |
usedExports | object[] | Exports always considered used |
usedClassMembers | (string | object)[] | Class method/property names the framework invokes at runtime; scoped objects { extends?, implements?, members } match only classes with matching heritage |
manifestEntries | object[] | Entry points derived from framework manifest files (see Manifest-derived entries) |
Verify the plugin
Confirm fallow picks up your plugin, and (for
manifestEntries) see exactly what it matched and seeded:fallow plugin-check is a read-only dry-run. It reports whether each plugin activated (with the unmet requirement when it did not), and for manifestEntries rules the manifests matched, each manifest’s when-gate result, the entries seeded (with a path_exists flag), and typed warnings[]. It always exits 0.Supported formats
| Format | Extension | Comments |
|---|---|---|
| JSONC | .jsonc | // and /* */ |
| JSON | .json | No |
| TOML | .toml | # |
Detection strategies
Plugins can use simple enablers or rich detection logic with boolean combinators.Simple detection (enablers)
Simple detection (enablers)
The simplest way to activate a plugin. Package names are checked against A trailing
package.json. The plugin activates if any enabler matches:/ enables prefix matching, so @myorg/ matches any package in the @myorg scope.File-based detection
File-based detection
Activate a plugin when specific config files exist in the project:
Combined detection (boolean logic)
Combined detection (boolean logic)
Combine multiple conditions with This plugin only activates when both
all (AND), any (OR), or not:@my-org/core is installed and a my-org.config.* file exists.Used exports
Mark specific exports as always-used for convention-based frameworks. These are exports consumed by name at runtime:Used class members
Mark class method or property names that the framework invokes at runtime via an interface or contract pattern. Listed names extend fallow’s built-in Angular and React lifecycle allowlist, so members implementing these contracts are never flagged as unused class members. Each entry is either a plain member name (global suppression) or a scoped object that only matches classes whose heritage clause includes the configuredextends or implements identifier:
agInit are unique enough to suppress globally. Common names like refresh or execute would produce false negatives on unrelated classes, so scope them with implements or extends. A scoped rule requires at least one of extends or implements; an unconstrained object rule is rejected at load time.
This is the right tool for libraries that call methods on consumer classes reflectively: ag-Grid’s AgFrameworkComponent<T>, TypeORM’s MigrationInterface (up, down), Web Components (connectedCallback, disconnectedCallback, attributeChangedCallback), or any strategy/plugin pattern where the library invokes your methods rather than your code doing so directly.
The allowlist only applies to class methods and properties. Enum members with the same names are still checked.
For project-wide allowlists that apply regardless of which packages are installed, use the top-level usedClassMembers field in your fallow config instead. See Configuration overview.
Manifest-derived entries
Some frameworks declare their entry points inside per-package manifest files rather than inpackage.json main/exports or a single app entry. A large monorepo can have hundreds of these, and the entry file for each is conditional on the manifest’s own fields. Static entryPoints globs cannot read those fields, so manifestEntries lets a plugin derive entries from the manifests themselves.
Each rule finds manifest files by a recursive glob, parses them (JSON or JSONC), and for every manifest that passes the rule’s when gate, resolves each entries[].path relative to that manifest’s directory into an entry point (using the plugin’s entryPointRole).
| Field | Type | Description |
|---|---|---|
manifests | string | Recursive glob selecting the manifest files to read |
format | "jsonc" | "json" | Manifest format. Defaults to jsonc, which also parses plain JSON |
when | object | Manifest-level gate: a map of dotted field path to an expected value. ALL must match by strict equality for the manifest to be processed. Empty matches every manifest |
entries | object[] | The entries to seed per matching manifest (each { path, when? }) |
entries[].path is a glob relative to the manifest’s directory. It must encode its own extension (public/index.{ts,tsx}), because entry-point globs are matched literally against discovered files (there is no source-extension probing). It may contain ${dotted.field} interpolation that fans out over the named manifest field: a string field yields one entry, an array field yields one entry per element, and a missing or empty field yields none. Each entry may carry its own when gate.
when matching is strict equality: { "plugin.browser": true } matches a manifest whose plugin.browser is literally true, and a manifest with plugin.browser: false is skipped for that entry.
Manifest discovery respects
.gitignore and skips node_modules, so manifests under ignored directories are not seen (their entries would not be analyzed anyway). Fallow emits a warning when a manifests glob matches nothing, when a when gate excludes every manifest, or when a field path resolves in none of the matched manifests (a likely typo). Run fallow plugin-check --format json to see, per rule, which manifests matched, which passed the when gate, the entries seeded (with path_exists), and any warnings; those warnings (manifests-matched-none, when-excluded-all, field-path-unresolved, entries-empty, manifest-parse-failed, entry-outside-root, seeded-paths-missing) are the fastest way to debug a rule that seeds nothing.Discovery order
Fallow searches for plugin files in this order:- Explicit paths from the
pluginsconfig field .fallow/plugins/directory- Project root:
fallow-plugin-*.{jsonc,json,toml}files
Using the plugins config field
Examples
React Router
ag-Grid Angular
ICellRendererAngularComp, which requires agInit(params) and refresh(params) methods that ag-Grid calls at runtime. agInit is unique to ag-Grid so it stays global. refresh is a common method name, so scoping it to classes that implements ICellRendererAngularComp keeps suppression off unrelated refresh() methods elsewhere in the codebase.
Internal tooling
External plugins cover most use cases. AST-based config parsing (like the built-in ESLint or Vite plugins do) requires a built-in Rust plugin.
JSON Schema
Generate the schema locally for IDE autocomplete. The schema is generated dynamically and doesn’t exist at a remote URL:See also
Built-in plugins
Browse the 123 built-in framework plugins.
fallow list
Verify active plugins and inspect project metadata.