Skip to content

Plugin Management

This guide explains how to manage plugins in Fast Pluggy.

Understanding Plugins

Plugins in Fast Pluggy are Python packages that extend the functionality of your FastAPI application. Each plugin can:

  • Add new routes and endpoints
  • Provide new UI components
  • Extend the admin interface
  • Add new database models
  • Integrate with external services

Installing Plugins

Plugins are ordinary Python packages, installed like any other:

pip install fastpluggy-auth-user  # example

FastPluggy auto-discovers every installed plugin via its entry point at startup — no config or registration step. Restart the server after installing (or removing) a plugin so it picks up the change.

Browsing and installing from the admin UI

Core itself doesn't ship a plugin browser/installer — that's a separate, optional plugin: fastpluggy-marketplace.

pip install fastpluggy-marketplace

Once installed (and the server restarted), a "Marketplace" entry appears in the admin menu: browse/search the plugin registry, check compatibility against your running FastPluggy version, and install/uninstall plugins from the web UI — admin-only, with live progress output.

Managing Plugins

Checking Plugin Status

You can check if a plugin is loaded:

# Get the module manager
module_manager = pluggy.module_manager

# Check if a module is loaded
is_loaded = module_manager.is_module_loaded("my-plugin")
print(f"Plugin loaded: {is_loaded}")

Executing Plugin Hooks

You can execute hooks for all plugins:

# Execute a hook for all modules
module_manager.execute_all_module_hook(hook_name="on_load_complete")

Refreshing Plugin States

To refresh the plugin states from the database:

module_manager.refresh_plugins_states()

Creating Plugins

Basic Plugin Structure

A Fast Pluggy plugin has the following structure:

my-plugin/
├── __init__.py
├── plugin.py
├── routes.py
├── models.py
└── templates/
    └── index.html

Plugin Class

Every plugin must define a class that inherits from FastPluggyBaseModule:

# plugin.py
from fastapi import APIRouter
from pathlib import Path
from typing import Optional, Dict, Any

from fastpluggy.core.module_base import FastPluggyBaseModule

def get_router():
    """
    Wrap router in a function to avoid circular imports & plugin dependencies issue on loading.
    """
    from .routes import router
    return router

class MyPlugin(FastPluggyBaseModule):
    # Identity and version
    module_name: str = "my-plugin"
    module_version: str = "0.1.0"
    depends_on: Dict[str, str] = {"fastpluggy": ">=0.1.0"}

    # Menu metadata
    module_menu_name: str = "My Plugin"
    module_menu_icon: str = "fas fa-puzzle-piece"
    module_menu_type: str = "main"  # or "admin"

    # Router configuration
    module_mount_url: Optional[str] = "/my-plugin"
    module_router: Any = get_router()

    # Lifecycle hooks
    def on_load_complete(self, fast_pluggy):
        """Called when all plugins are loaded."""
        # Initialize your plugin here
        pass

    def after_setup_templates(self, fast_pluggy):
        """Called when templates are set up."""
        # Add custom template globals or filters
        pass

Plugin Routes

Define routes in your plugin:

# routes.py
from fastapi import APIRouter

router = APIRouter(prefix="/my-plugin")

@router.get("/")
def index():
    return {"message": "Hello from my plugin!"}

Plugin Models

Define database models for your plugin:

# models.py
from sqlalchemy import Column, Integer, String
from fastpluggy.core.models_tools.sqlalchemy import Base

class MyModel(Base):
    __tablename__ = "my_plugin_items"

    id = Column(Integer, primary_key=True, index=True)
    name = Column(String, index=True)
    description = Column(String)

Tables are created, not migrated

At plugin load FastPluggy calls create_table_if_not_exist for your models — a CREATE TABLE … IF NOT EXISTS, which does not ALTER a table that already exists. So adding or changing a column on an existing model does not update the live table: the new column simply won't be there and queries against it fail. FastPluggy ships no migration framework — handle schema changes yourself (an explicit ALTER TABLE, an Alembic migration, or, in development, drop the table and let it be recreated on the next load).

Plugin Dependencies

Plugins can depend on other plugins by specifying them in the depends_on dictionary:

class MyPlugin(FastPluggyBaseModule):
    # ...
    depends_on: Dict[str, str] = {
        "fastpluggy": ">=0.1.0",
        "auth-plugin": ">=0.2.0"
    }
    # ...

Plugin Configuration

Plugins can define and access configuration through the module_settings attribute:

from fastpluggy.core.config import BaseDatabaseSettings

# Define your plugin's settings model
class MyPluginSettings(BaseDatabaseSettings):
    api_key: str = ""
    debug: bool = False
    max_items: int = 100

class MyPlugin(FastPluggyBaseModule):
    # ...
    module_settings: Optional[MyPluginSettings] = None

    def on_load_complete(self, fast_pluggy):
        # Initialize settings if not already set
        if not self.module_settings:
            self.module_settings = MyPluginSettings()

        # Access settings
        api_key = self.module_settings.api_key
        debug = self.module_settings.debug

        # Use configuration values
        if debug:
            print(f"Debug mode enabled, API key: {api_key}")

Settings can also be loaded from the database or environment variables by extending the configuration system.

Topbar Actions

Plugins can add icon buttons to the admin topbar (the sticky header) by registering TopbarAction entries in the GlobalRegistry. Buttons appear on the right-hand side of the topbar, sorted by position (lower = leftmost).

Action modes

Three mutually exclusive modes, evaluated in priority order:

Mode Field Behaviour
Modal modal_widgets Widget list rendered server-side into a Bootstrap modal. The button uses data-bs-toggle="modal" — no JS required.
JS js_onclick Arbitrary JS expression in onclick="...".
Link url Plain <a> navigation link.

Pass a list of widget instances to modal_widgets. The framework pre-renders them into a dedicated <div class="modal" id="fp-modal-{id}"> in the page HTML at request time. Clicking the button opens the modal via Tabler/Bootstrap's data-bs-toggle — no JavaScript needed.

Each widget is deep-copied and processed per request, so shared instances registered at startup are safe across concurrent requests.

from fastpluggy.core.global_registry import GlobalRegistry
from fastpluggy.core.topbar import TopbarAction
from fastpluggy.core.widgets.categories.input.button_list import ButtonListWidget
from fastpluggy.core.widgets.categories.input.button import AutoLinkWidget

class MyPlugin(FastPluggyBaseModule):
    ...
    def on_load_complete(self, fast_pluggy, plugin):
        GlobalRegistry.extend_globals('topbar_actions', items=[
            TopbarAction(
                id='my-plugin-topbar',
                icon='ti ti-star',
                tooltip='My Plugin',
                position=20,
                modal_widgets=[
                    {
                        'widget': ButtonListWidget,
                        'kwargs': {
                            'title': "My Plugin",
                            'buttons': [
                                AutoLinkWidget(route_name="my_index", label="Home"),
                                AutoLinkWidget(route_name="my_settings", label="Settings"),
                            ],
                        },
                    },
                ],
                module_name='my_plugin',
            )
        ])

The rendered button:

<button class="fp-icon-btn" id="my-plugin-topbar" type="button"
        title="My Plugin"
        data-bs-toggle="modal" data-bs-target="#fp-modal-my-plugin-topbar">
    <i class="ti ti-star"></i>
</button>

Tip

debug_tools uses this pattern: its bug-icon button opens a modal with a ButtonListWidget of quick-access debug links (env vars, routes, registry, etc.).


JS action

TopbarAction(
    id='my-action',
    icon='ti ti-player-play',
    tooltip='Run task',
    position=30,
    js_onclick='myRunTask()',
    module_name='my_plugin',
)

The JS function must be available on the page. Deliver it via extra_js_files:

class MyPlugin(FastPluggyBaseModule):
    extra_js_files: list = ['/plugin-pkg/my_plugin/static/js/actions.js']

TopbarAction(
    id='open-docs',
    icon='ti ti-book',
    tooltip='Documentation',
    position=80,
    url='/docs',
)

TopbarAction field reference

Field Type Default Description
id str Unique HTML id for the <button> or <a>.
icon str CSS icon classes, e.g. "ti ti-bug" or "fas fa-wrench".
tooltip str "" title attribute shown on hover.
position int 50 Sort order — lower values appear further left.
modal_widgets list [] Widget instances pre-rendered into a server-side modal. Takes priority over js_onclick and url.
js_onclick str \| None None JS expression in onclick="...". Takes priority over url.
url url \| None None Navigation URL (renders as <a>).
extra_classes str "" Additional CSS classes on the button element.
module_name str "" Your plugin's module_name — links the action to the plugin admin overview.

Next Steps

After setting up plugins, check out the API Reference for more details on the Fast Pluggy API.