Plugin Authoring Guide
May 4, 2026 ยท View on GitHub
Fallow supports external plugin definitions that let you add framework and tool support without writing Rust code. External plugins provide the same declarative capabilities as built-in plugins.
Quick Start
Create a file named fallow-plugin-<name>.jsonc in your project root:
{
"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/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"] }
]
}
That's it. Fallow automatically discovers fallow-plugin-* files in your project root.
Supported Formats
| Format | Extension | Comments | $schema support |
|---|---|---|---|
| JSONC | .jsonc | // and /* */ | Yes |
| JSON | .json | No | Yes |
| TOML | .toml | # | No |
All formats use camelCase field names. We recommend JSONC for its comment support and $schema IDE autocomplete. Generate the schema with:
fallow plugin-schema
Plugin File Format
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 framework entry point files |
configPatterns | string[] | Glob patterns for config files (marked always-used) |
alwaysUsed | string[] | Glob patterns for files always considered used |
toolingDependencies | string[] | Packages used via CLI, not source imports |
detection | object | Rich activation logic (dependency, fileExists, all/any) |
usedExports | object[] | Exports always considered used in matching files |
enablers
Package names checked against package.json dependencies. The plugin activates if any enabler matches. Only used when detection is not set.
Supports prefix matching with a trailing /:
{
"enablers": ["@myorg/"] // matches @myorg/core, @myorg/cli, etc.
}
detection
Rich activation logic with boolean combinators. Takes priority over enablers when set.
{
// Activate when a specific package is installed
"detection": { "type": "dependency", "package": "next" }
}
{
// Activate when a config file exists
"detection": { "type": "fileExists", "pattern": "nuxt.config.*" }
}
{
// Combine conditions
"detection": {
"type": "all",
"conditions": [
{ "type": "dependency", "package": "@my-org/core" },
{ "type": "fileExists", "pattern": "my-org.config.*" }
]
}
}
entryPoints
Glob patterns for files that serve as entry points to your application. These files are never flagged as unused, and their imports are traced through the module graph.
{
"entryPoints": [
"src/routes/**/*.{ts,tsx}",
"src/middleware.{ts,js}",
"src/plugins/**/*.ts"
]
}
configPatterns
Glob patterns for framework config files. When the plugin is active, these files are marked as always-used (they won't be flagged as unused files).
{
"configPatterns": [
"my-framework.config.{ts,js,mjs}",
".my-frameworkrc.{json,yaml}"
]
}
alwaysUsed
Files that should always be considered used when this plugin is active, even if nothing imports them.
{
"alwaysUsed": [
"src/setup.ts",
"public/**/*",
"src/global.d.ts"
]
}
toolingDependencies
Packages that are tooling dependencies -- used via CLI commands or config files, not imported in source code. These won't be flagged as unused dev dependencies.
{
"toolingDependencies": [
"my-framework-cli",
"@my-framework/dev-tools"
]
}
usedExports
Exports that are always considered used for files matching a glob pattern. Use this for convention-based frameworks where specific export names have special meaning.
Use "*" when every export in matching convention files is consumed by the framework.
{
"usedExports": [
{ "pattern": "src/routes/**/*.{ts,tsx}", "exports": ["default", "loader", "action", "meta"] },
{ "pattern": "src/**/*.stories.{ts,tsx}", "exports": ["*"] },
{ "pattern": "src/middleware.ts", "exports": ["default"] }
]
}
Discovery
Fallow discovers external plugins in this order (first occurrence of a plugin name wins):
- Explicit paths from the
pluginsconfig field .fallow/plugins/directory -- all*.jsonc,*.json,*.tomlfiles- Project root --
fallow-plugin-*.{jsonc,json,toml}files
Using the plugins config field
Point to specific plugin files or directories:
// .fallowrc.json
{
"plugins": [
"tools/fallow-plugins/",
"vendor/my-plugin.jsonc",
"vendor/another-plugin.json"
]
}
Using .fallow/plugins/
Place plugin files in .fallow/plugins/ for automatic discovery:
my-project/
.fallow/
plugins/
my-framework.jsonc
custom-tool.json
src/
package.json
Using project root
Name plugin files with the fallow-plugin- prefix:
my-project/
fallow-plugin-my-framework.jsonc
fallow-plugin-custom-tool.json
src/
package.json
Examples
React Router / TanStack Router
{
"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/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/routes/**/*.{ts,tsx}", "exports": ["default", "loader", "action", "meta", "handle", "shouldRevalidate"] },
{ "pattern": "app/routes/**/*.{ts,tsx}", "exports": ["default", "loader", "action", "meta", "handle", "shouldRevalidate"] }
]
}
Custom CMS
{
"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/plugin-schema.json",
"name": "my-cms",
"enablers": ["@my-cms/core"],
"entryPoints": ["content/**/*.{ts,tsx}", "schemas/**/*.ts"],
"alwaysUsed": ["cms.config.ts", "content/**/*.mdx"],
"configPatterns": ["cms.config.{ts,js}"],
"toolingDependencies": ["@my-cms/cli"],
"usedExports": [
{ "pattern": "content/**/*.{ts,tsx}", "exports": ["default", "metadata", "getStaticProps"] }
]
}
Internal Tooling
{
// Internal build system plugin
"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/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",
"@internal/test-utils"
]
}
Sharing Plugins
External plugins are plain files -- share them however you share config:
- Git: check
fallow-plugin-*files into your repo - Monorepo: put shared plugins in a central
tools/directory and reference viapluginsconfig - npm package: publish a package containing plugin files, then reference them:
plugins = ["node_modules/@my-org/fallow-plugins/"]
JSON Schema
Generate the JSON Schema for plugin files to enable IDE autocomplete and validation:
fallow plugin-schema > plugin-schema.json
Reference it in your plugin files:
{
"$schema": "./plugin-schema.json",
"name": "my-plugin",
"enablers": ["my-pkg"]
}
Built-in vs External Plugins
| Capability | Built-in | External |
|---|---|---|
| Entry points | Yes | Yes |
| Always-used files | Yes | Yes |
| Used exports | Yes | Yes |
| Tooling dependencies | Yes | Yes |
| Config file patterns | Yes | Yes |
| AST-based config parsing | Yes | No |
| Custom detection logic | Yes | Yes (dependency, fileExists, all/any combinators) |
External plugins cover the vast majority of use cases. AST-based config parsing (extracting entry points from vite.config.ts, resolving ESLint plugin short names, etc.) requires a built-in Rust plugin.
Verifying
Check that your plugin is detected:
fallow list --plugins
This shows all active plugins, including external ones.