This document explains the hot-loadable plugin system that provides extensibility to the FastAPI Best Architecture without modifying core code. It covers plugin discovery, configuration validation, router injection mechanisms, dependency management, runtime status checking, and lifecycle operations including installation and uninstallation.
For information about specific built-in plugins (OAuth2, Dict, System Config, Code Generator, etc.), see System Plugins Overview. For OAuth2 plugin implementation details, see OAuth2 Plugin. For Dictionary plugin implementation, see Dictionary Plugin.
The system supports two distinct plugin types that differ in how they integrate with the application's routing structure:
Extend-level plugins inject routes into existing routers within the application. They augment functionality of established modules like /admin without creating new top-level routes.
Configuration Requirements:
[api] section in plugin.toml with route configurationsapp.extend indicating target app module (e.g., "admin")Examples: RBAC/Casbin Plugin, Dict Plugin (extends /admin/sys)
Application-level plugins create new top-level routes that function as independent modules with their own API namespace.
Configuration Requirements:
app.router list specifying router object names[api] section neededExamples: OAuth2 Plugin (/oauth2/*), System Config Plugin, Code Generator Plugin, Email Plugin
Plugin Type Determination Diagram
Sources: backend/plugin/tools.py115-173
Plugins must follow a specific directory structure under PLUGIN_DIR:
backend/plugin/
├── plugin_name/
│ ├── __init__.py # Required: Python package marker
│ ├── plugin.toml # Required: Plugin metadata and configuration
│ ├── README.md # Required: Plugin documentation
│ ├── requirements.txt # Optional: Python dependencies
│ ├── api/ # API routes (extend-level) or router module (app-level)
│ ├── model.py # Optional: SQLAlchemy models
│ ├── service/ # Optional: Business logic
│ ├── crud/ # Optional: Database operations
│ └── sql/ # Optional: Database initialization scripts
│ ├── mysql/
│ │ ├── init.sql
│ │ └── init_snowflake.sql
│ └── postgresql/
│ ├── init.sql
│ └── init_snowflake.sql
The plugin.toml file uses TOML format and must contain specific sections:
Required Fields:
| Section | Field | Type | Description |
|---|---|---|---|
[plugin] | summary | string | Short plugin description |
[plugin] | version | string | Semantic version (e.g., "1.0.0") |
[plugin] | description | string | Detailed description |
[plugin] | author | string | Plugin author name |
Extend-Level Plugin Additional Fields:
| Section | Field | Type | Description |
|---|---|---|---|
[app] | extend | string | Target app module name (e.g., "admin") |
[api] | {filename} | object | Per-API-file routing configuration |
[api.{filename}] | prefix | string | URL prefix for routes (e.g., "/casbin") |
[api.{filename}] | tags | string | OpenAPI tag for grouping |
Application-Level Plugin Additional Fields:
| Section | Field | Type | Description |
|---|---|---|---|
[app] | router | list | Router object names to include (e.g., ["router_v1"]) |
Discovery Flow Diagram
The get_plugins() function scans PLUGIN_DIR and returns a list of valid plugin package names:
Configuration Validation:
The parse_plugin_config() function validates each plugin's configuration:
summary, version, description, author exist[api] section and app.extendapp.router listPluginConfigError if validation failsSources: backend/plugin/tools.py40-55 backend/plugin/tools.py100-112 backend/plugin/tools.py115-173
Router injection occurs during application startup via build_final_router(), which orchestrates the injection of both extend-level and app-level plugins into the routing tree.
Extend-Level Injection Flow
Key Steps:
backend/plugin/{plugin_name}/api/ exists.py files (excluding __init__.py)prefix and tags from plugin.toml[api][filename]import_module_cached() to load the plugin's router modulebackend/plugin/{name}/api/v1/casbin.pybackend/app/admin/api/v1/casbin.py (if app.extend = "admin")target_router.include_router() with PluginStatusChecker dependencyExample from Dict Plugin:
Configuration in plugin.toml:
Injection result:
backend/plugin/dict/api/dict_data.py router → injected into backend/app/admin/api/v1/sys/ with prefix /dict-databackend/plugin/dict/api/dict_type.py router → injected into backend/app/admin/api/v1/sys/ with prefix /dict-typesSources: backend/plugin/tools.py176-233
Application-Level Injection Flow
Key Steps:
backend/plugin/{plugin_name}/api/router.pyapp.router list from plugin configurationgetattr() to retrieve each named router from the moduleAPIRouterPluginStatusChecker dependencyExample from OAuth2 Plugin:
Configuration in plugin.toml:
Implementation in backend/plugin/oauth2/api/router.py:
Result: All routes in router_v1 become available under /oauth2/v1/* in the main application.
Sources: backend/plugin/tools.py236-262
The build_final_router() function coordinates the entire injection process:
Build Process Sequence
Critical Ordering: The main router import must occur after extend-level injection but before app-level injection. This ensures:
Sources: backend/plugin/tools.py265-278
The PluginStatusChecker class provides per-request plugin status verification, enabling plugins to be enabled or disabled without restarting the application.
Status Checker Architecture
Class Definition:
The PluginStatusChecker is a callable dependency injected into all plugin routes:
Injection Examples:
Extend-level:
App-level:
Status Storage Format in Redis:
Key: {PLUGIN_REDIS_PREFIX}:{plugin_name} (e.g., fba:plugin:oauth2)
Value (JSON):
The status checking mechanism allows runtime plugin control:
PUT /admin/v1/sys/plugins/{plugin}/status"1" ↔ "0"No application restart required because status is checked per-request from Redis cache.
Sources: backend/plugin/tools.py430-456 backend/plugin/tools.py226-230 backend/plugin/tools.py260 backend/app/admin/service/plugin_service.py81-100
Plugins can specify Python package dependencies in requirements.txt, which are automatically installed during plugin installation.
Dependency Installation Flow
Step-by-Step:
backend/plugin/{plugin}/requirements.txtpackaging.requirements.Requirement() to parse each dependencyimportlib.metadata.distribution() to verify if package is already installed_ensure_pip_available() which attempts:
python -m pip --versionpython -m ensurepip --default-piphttps://bootstrap.pypa.io/get-pip.pypython -m pip install -r requirements.txt [-i {PLUGIN_PIP_INDEX_URL}]
PLUGIN_PIP_MAX_RETRY times (default: 3)Configuration Options:
| Setting | Type | Description |
|---|---|---|
PLUGIN_PIP_CHINA | bool | Use China mirror for faster downloads |
PLUGIN_PIP_INDEX_URL | str | PyPI mirror URL (default: Aliyun mirror) |
PLUGIN_PIP_MAX_RETRY | int | Maximum retry attempts on failure (default: 3) |
The uninstall_requirements() function removes plugin dependencies:
Important Notes:
-y flag for non-interactive executionBoth installation and uninstallation have async wrappers for use in async contexts:
install_requirements_async() - Uses run_in_threadpool() to avoid blocking event loopuninstall_requirements_async() - Similarly wrapped for async executionWindows Limitation: Due to Windows asyncio subprocess limitations, these use thread pool execution rather than true async subprocess handling.
Sources: backend/plugin/tools.py281-334 backend/plugin/tools.py336-392 backend/plugin/tools.py394-407 backend/plugin/tools.py410-427
The system provides comprehensive APIs for managing plugin lifecycle operations including installation, uninstallation, status updates, and packaging.
Plugins can be installed from two sources:
Installation Architecture
The install_zip_plugin() function handles ZIP archive installation:
Validation Steps:
zipfile.is_zipfile(){plugin_dir}/plugin.toml{plugin_dir}/README.md^([a-zA-Z0-9_]+)Extraction Process:
PLUGIN_DIRplugin_name/api/router.py → api/router.pyThe install_git_plugin() function handles Git repository installation:
Process:
is_git_url() regex to validate formatdulwich.porcelain.clone() with checkout=TrueSupported URL Formats:
https://github.com/user/repo.gitgit@github.com:user/repo.gitBoth installation methods complete with:
install_requirements_async(plugin_name){PLUGIN_REDIS_PREFIX}:changed to "ture" (sic)Sources: backend/utils/file_ops.py84-138 backend/utils/file_ops.py140-163 backend/app/admin/service/plugin_service.py42-59
The uninstallation process provides safe plugin removal with backup:
Uninstallation Flow
Process Details:
ENVIRONMENT == 'dev' for safetyuninstall_requirements_async(plugin)backend/plugin/{plugin}/backend/plugin/{plugin}.{YYYYMMDDHHMMSS}.backup/Safety Features:
Sources: backend/app/admin/service/plugin_service.py62-78
Plugin status can be toggled between enabled and disabled states without restarting the application:
Status Update Flow
Implementation:
Effect:
The system can package installed plugins as ZIP archives for distribution:
Packaging Process:
__pycache__StreamingResponse with appropriate headersAPI Endpoint:
GET /admin/v1/sys/plugins/{plugin}
Response: application/x-zip-compressed
Filename: {plugin}.zip
Sources: backend/app/admin/service/plugin_service.py103-124 backend/app/admin/api/v1/sys/plugin.py82-89
Complete plugin management API:
| Method | Endpoint | Permission | Purpose |
|---|---|---|---|
| GET | /admin/v1/sys/plugins | JWT Auth | List all plugins with metadata |
| GET | /admin/v1/sys/plugins/changed | JWT Auth | Check if restart needed |
| POST | /admin/v1/sys/plugins | sys:plugin:install | Install from ZIP or Git |
| DELETE | /admin/v1/sys/plugins/{plugin} | sys:plugin:uninstall | Uninstall (backup + cleanup) |
| PUT | /admin/v1/sys/plugins/{plugin}/status | sys:plugin:edit | Toggle enable/disable |
| GET | /admin/v1/sys/plugins/{plugin} | JWT Auth | Download as ZIP |
Sources: backend/app/admin/api/v1/sys/plugin.py1-89
Plugin information is cached in Redis for fast access and cross-instance coordination:
Primary Keys:
{PLUGIN_REDIS_PREFIX}:{plugin_name} - Individual plugin information{PLUGIN_REDIS_PREFIX}:changed - Change detection flagDefault PLUGIN_REDIS_PREFIX is "fba:plugin".
During application startup, parse_plugin_config() performs cache synchronization:
Cache Sync Process:
Key Behaviors:
delete_prefix() with exclusion listenable = "1"plugin.tomlModification Operations:
| Operation | Cache Update |
|---|---|
| Install | SET changed = "ture" (triggers restart detection) |
| Uninstall | DELETE {plugin} + SET changed = "ture" |
| Status Update | SET {plugin} (modified enable field only) |
| Startup Sync | SET {plugin} (all plugins) + DELETE changed |
The changed key enables restart detection:
Detection Flow:
Use Case: After installing, uninstalling, or modifying plugin files, the system sets this flag. Frontend polls to detect when restart is required to apply changes.
Sources: backend/plugin/tools.py115-173 backend/app/admin/service/plugin_service.py27-39
Plugins can include database initialization scripts for creating tables, indexes, and seeding data:
SQL scripts must follow this directory structure:
backend/plugin/{plugin_name}/sql/
├── mysql/
│ ├── init.sql # For autoincrement primary keys
│ └── init_snowflake.sql # For Snowflake ID primary keys
└── postgresql/
├── init.sql # For autoincrement primary keys
└── init_snowflake.sql # For Snowflake ID primary keys
The get_plugin_sql() function selects the appropriate script based on configuration:
Selection Logic:
Parameters:
db_type: DataBaseType.mysql or DataBaseType.postgresqlpk_type: PrimaryKeyType.autoincrement or PrimaryKeyType.snowflakeThe parse_sql_script() utility function validates and parses SQL scripts:
Validation Rules:
sqlparse.split() to separate statementsSELECT and INSERT statements allowed
Safety Note: This validation is primarily for the CLI fba --sql command. Plugin installation executes scripts directly via database connection without parsing restrictions.
Sources: backend/plugin/tools.py71-97 backend/utils/file_ops.py166-187
The plugin system integrates with multiple core systems:
Plugins are discovered and injected during application startup:
build_final_router() called in application factoryFor details on application startup sequence, see Application Bootstrap & Lifecycle.
All plugin routes automatically inherit security dependencies:
plugin.tomlPluginStatusChecker added as additional dependencyFor security implementation details, see RBAC & Permission System.
Plugins can define their own models:
get_plugin_models() discovers all plugin model classesFor database integration details, see Database Schema & Initialization.
Plugin management exposed via CLI:
fba add command installs plugins from ZIP or Gitfba --sql command executes plugin SQL scriptsWeb UI for plugin management:
All operations require appropriate RBAC permissions (sys:plugin:*).
Core Functions:
| Function | File | Purpose |
|---|---|---|
get_plugins() | backend/plugin/tools.py40-55 | Discover plugin packages in PLUGIN_DIR |
load_plugin_config() | backend/plugin/tools.py100-112 | Load and parse plugin.toml |
parse_plugin_config() | backend/plugin/tools.py115-173 | Validate configs, classify plugins, sync to Redis |
inject_extend_router() | backend/plugin/tools.py176-233 | Inject extend-level plugin routes |
inject_app_router() | backend/plugin/tools.py236-262 | Inject app-level plugin routes |
build_final_router() | backend/plugin/tools.py265-278 | Orchestrate complete injection process |
install_requirements() | backend/plugin/tools.py336-392 | Install plugin dependencies via pip |
uninstall_requirements() | backend/plugin/tools.py394-407 | Remove plugin dependencies |
Core Classes:
| Class | File | Purpose |
|---|---|---|
PluginStatusChecker | backend/plugin/tools.py430-456 | FastAPI dependency for runtime status checking |
PluginService | backend/app/admin/service/plugin_service.py23-127 | Service layer for plugin lifecycle operations |
Exception Classes:
| Exception | Raised When |
|---|---|
PluginConfigError | Invalid or incomplete plugin.toml configuration |
PluginInjectError | Router injection fails or plugin status check fails |
PluginInstallError | Dependency installation or uninstallation fails |
File Operations:
| Function | File | Purpose |
|---|---|---|
install_zip_plugin() | backend/utils/file_ops.py84-138 | Install plugin from ZIP archive |
install_git_plugin() | backend/utils/file_ops.py140-163 | Install plugin from Git repository |
get_plugin_sql() | backend/plugin/tools.py71-97 | Locate SQL initialization script |
parse_sql_script() | backend/utils/file_ops.py166-187 | Parse and validate SQL script file |
API Endpoints:
| Endpoint | File | Description |
|---|---|---|
GET /admin/v1/sys/plugins | backend/app/admin/api/v1/sys/plugin.py18-21 | List all plugins |
GET /admin/v1/sys/plugins/changed | backend/app/admin/api/v1/sys/plugin.py24-27 | Check restart required |
POST /admin/v1/sys/plugins | backend/app/admin/api/v1/sys/plugin.py30-50 | Install plugin |
DELETE /admin/v1/sys/plugins/{plugin} | backend/app/admin/api/v1/sys/plugin.py53-66 | Uninstall plugin |
PUT /admin/v1/sys/plugins/{plugin}/status | backend/app/admin/api/v1/sys/plugin.py69-79 | Toggle status |
GET /admin/v1/sys/plugins/{plugin} | backend/app/admin/api/v1/sys/plugin.py82-89 | Download plugin |
Refresh this wiki
This wiki was recently refreshed. Please wait 6 days to refresh again.