Skip to content

Settings

App-wide settings (FastPluggyConfig)

Stored in the database and editable in the admin UI at /admin/settings. Values can also be set via environment variables (prefix: as configured by pydantic-settings).

Key Env var Default Description
app_name APP_NAME "FastPluggy" Displayed in sidebar and topbar
fp_admin_base_url FP_ADMIN_BASE_URL "/" Admin URL prefix
debug DEBUG False Show tracebacks on error pages
fp_plugins FP_PLUGINS "*" Comma-separated entry-point plugin names, or "*" for all
show_empty_menu_entries False Show sidebar parents with no visible children
admin_enabled True Mount the FastPluggy admin UI
include_in_schema_fp False Include FastPluggy routes in OpenAPI schema

Env-only settings (not admin-UI-editable)

These four are read directly from the environment before the database is even probed, so the app-level middleware they configure (SessionMiddleware, GZipMiddleware) can be installed once at boot — required for the degraded-mode self-heal (fast_pluggy#22) to work at all, since a middleware can never be added again once the app has served its first request. A value saved for these via /admin/settings is silently never read.

Key Env var Default Description
session_secret_key SESSION_SECRET_KEY Required for session middleware
session_https_only SESSION_HTTPS_ONLY false Marks the fp_session cookie Secure. Required when the app is served over HTTPS and uses OIDC — authlib stores the OIDC state/nonce in this session and the IdP callback is a cross-site redirect, so Safari/iOS drops a non-Secure cookie and login fails with a state mismatch.
gzip_enabled FASTPLUGGY_GZIP_ENABLED true Enable response gzip compression
gzip_minimum_size FASTPLUGGY_GZIP_MINIMUM_SIZE 500 Minimum response size (bytes) before compressing

Per-plugin settings

Declare a Pydantic BaseSettings class and assign it to module_settings. FastPluggy instantiates it at startup and persists values to the database.

from pydantic_settings import BaseSettings
from fastpluggy.core.module_base import FastPluggyBaseModule

class MySettings(BaseSettings):
    api_key:   str = ""
    max_items: int = 100
    namespace: str = "my_plugin"   # used as DB key prefix — keep unique

    class Config:
        env_prefix = "MY_PLUGIN_"

class MyPlugin(FastPluggyBaseModule):
    module_settings = MySettings   # pass the class, not an instance

The settings form is auto-generated and available at /base_module/{module_name}/settings.

Passing an instance fails silently

PluginState checks isinstance(module_settings, type) before using it. Pass an instance and it never raises and never calls it — it appends "Settings should be a class, not instance." to the plugin's error, which surfaces only as a warning triangle on the menu icon.

Omitting module_settings entirely is quieter still: the framework reports the plugin as having no settings, while the configuration keeps working through env vars and defaults. It reads as "this plugin has no configuration" rather than as a wiring bug, so nothing looks broken.

Worth a structural test — and it must read the field on an instance, since FastPluggyBaseModule is a Pydantic model.

Accessing settings at runtime

# From a lifecycle hook (fast_pluggy injected):
def on_load_complete(self, fast_pluggy):
    manager = fast_pluggy.get_manager()
    state   = manager.modules.get(self.module_name)
    cfg     = state.settings   # MySettings instance with DB-persisted values

# From a route handler:
@router.get("/my-route")
def my_route(fast_pluggy=Depends(get_fastpluggy)):
    state = fast_pluggy.get_manager().modules.get("my_plugin")
    cfg   = state.settings

namespace field

The namespace field (if present) is used as the DB key prefix so multiple plugins don't overwrite each other's settings. Set it to module_name and make it read-only in the form:

FormWidget(model=MySettings, data=current_settings, readonly_fields=["namespace"])