Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.fallow.tools/llms.txt

Use this file to discover all available pages before exploring further.

Create a declarative plugin file for any internal framework or tool fallow doesn’t cover. Plugins are JSON or TOML files that tell fallow about your framework’s conventions.
Start with JSONC format for $schema support and IDE autocomplete.

Quick start

1

Create the plugin file

Create a fallow-plugin-<name>.jsonc file in your project root:
{
  "$schema": "./plugin-schema.json",
  "name": "my-framework",
  "enablers": ["my-framework"],
  "entryPoints": ["src/routes/**/*.{ts,tsx}"],
  "alwaysUsed": ["src/setup.ts"],
  "toolingDependencies": ["my-framework-cli"],
  "usedExports": [
    { "pattern": "src/routes/**/*.{ts,tsx}", "exports": ["default", "loader", "action"] }
  ]
}
Fallow auto-discovers fallow-plugin-* files in your project root.
2

Configure the fields

Customize the plugin fields for your framework. Only name is required. Add the fields relevant to your use case.

Required

FieldTypeDescription
namestringUnique plugin name (shown in fallow list --plugins)

Optional

FieldTypeDescription
enablersstring[]Package names that activate this plugin
entryPointsstring[]Glob patterns for entry point files
configPatternsstring[]Glob patterns for config files (always-used)
alwaysUsedstring[]Files always considered used
toolingDependenciesstring[]Packages used via CLI, not imports
detectionobjectRich activation logic (see below)
usedExportsobject[]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
3

Verify the plugin

Confirm fallow picks up your plugin:
fallow list --plugins

Supported formats

FormatExtensionComments
JSONC.jsonc// and /* */
JSON.jsonNo
TOML.toml#

Detection strategies

Plugins can use simple enablers or rich detection logic with boolean combinators.
The simplest way to activate a plugin. Package names are checked against package.json. The plugin activates if any enabler matches:
{
  "enablers": ["my-framework", "@myorg/"]  // prefix matching with trailing /
}
A trailing / enables prefix matching, so @myorg/ matches any package in the @myorg scope.
Activate a plugin when specific config files exist in the project:
{
  "detection": { "type": "fileExists", "pattern": "nuxt.config.*" }
}
Combine multiple conditions with all (AND), any (OR), or not:
{
  "detection": {
    "type": "all",
    "conditions": [
      { "type": "dependency", "package": "@my-org/core" },
      { "type": "fileExists", "pattern": "my-org.config.*" }
    ]
  }
}
This plugin only activates when both @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:
{
  "usedExports": [
    { "pattern": "src/routes/**/*.{ts,tsx}", "exports": ["default", "loader", "action", "meta"] }
  ]
}

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 configured extends or implements identifier:
{
  "name": "ag-grid",
  "enablers": ["ag-grid-angular"],
  "usedClassMembers": [
    "agInit",
    { "implements": "ICellRendererAngularComp", "members": ["refresh"] }
  ]
}
Plain names like 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.

Discovery order

Fallow searches for plugin files in this order:
  1. Explicit paths from the plugins config field
  2. .fallow/plugins/ directory
  3. Project root: fallow-plugin-*.{jsonc,json,toml} files

Using the plugins config field

// .fallowrc.json
{
  "plugins": [
    "tools/fallow-plugins/",
    "vendor/my-plugin.jsonc"
  ]
}

Examples

React Router

{
  "$schema": "./plugin-schema.json",
  "name": "react-router",
  "enablers": ["react-router", "@tanstack/react-router"],
  "entryPoints": ["src/routes/**/*.{ts,tsx}", "app/routes/**/*.{ts,tsx}"],
  "configPatterns": ["react-router.config.{ts,js}"],
  "toolingDependencies": ["@react-router/dev"],
  "usedExports": [
    { "pattern": "{src,app}/routes/**/*.{ts,tsx}", "exports": ["default", "loader", "action", "meta", "handle", "shouldRevalidate"] }
  ]
}

ag-Grid Angular

{
  "$schema": "./plugin-schema.json",
  "name": "ag-grid",
  "enablers": ["ag-grid-angular"],
  "usedClassMembers": [
    "agInit",
    { "implements": "ICellRendererAngularComp", "members": ["refresh"] }
  ]
}
ag-Grid Angular cell renderers implement 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

{
  "$schema": "./plugin-schema.json",
  "name": "our-build-system",
  "enablers": ["@internal/build"],
  "configPatterns": ["build.config.{ts,js}", ".buildrc"],
  "alwaysUsed": ["scripts/build/**/*.ts", "config/**/*.ts"],
  "toolingDependencies": ["@internal/build", "@internal/lint-rules"]
}
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:
fallow plugin-schema > plugin-schema.json
Then reference it in your plugin file:
{
  "$schema": "./plugin-schema.json"
}

See also

Built-in plugins

Browse the 94 built-in framework plugins.

fallow list

Verify active plugins and inspect project metadata.