Settings

This part covers how FlaskBB’s settings system works. This is especially useful if you plan on developing a plugin or want to contribute to FlaskBB itself.

Settings are not hardcoded config values - they’re a registry of declarative definitions backed by DB rows. A setting is defined once as a SettingDefinition grouped into a SettingGroup, registered into the SettingsRegistry at startup, and its value is persisted as a Setting row.

Registering setting groups

Setting groups are registered via two pluggy hooks, both defined in flaskbb.plugins.spec:

flaskbb_load_internal_setting_groups

FlaskBB’s own hook for its built-in setting groups (general, auth, misc, appearance, …). Plugin authors must not implement this hook.

flaskbb_load_setting_groups

The hook third-party plugins implement to register their own SettingGroup. See Developing new Plugins for a full example.

Which hook found a group determines where it shows up in the admin UI - “FlaskBB Settings” for the internal hook, “Plugin Settings” for the plugin hook - nothing about the group itself controls this.

Both hooks collect every implementation’s return value (a single SettingGroup or a list of them); there’s no firstresult.

class flaskbb.core.settings.definitions.SettingGroup(key: str, name: str, description: str, settings: tuple[flaskbb.core.settings.definitions.SettingDefinition, ...])[source]

Setting definitions

A SettingDefinition is never used directly - pick the subclass matching the value’s type. Each subclass knows how to render itself as a WTForms field (wtf_field()) and how to serialize/deserialize its value to/from the JSON text stored in the DB.

Definition

Rendered As

Parsed & Saved as

StringSetting

wtforms.fields.StringField

str

IntSetting

wtforms.fields.IntegerField

int

BoolSetting

wtforms.fields.BooleanField

bool

SelectSetting

wtforms.fields.SelectField

single value

SelectMultipleSetting

wtforms.fields.SelectMultipleField

list

class flaskbb.core.settings.definitions.SettingDefinition(key: str, value: Any, name: str, description: str)[source]

Base class for a single setting. Not used directly - use one of the value_type-specific subclasses below (IntSetting, BoolSetting, …).

wtf_field()[source]

Return a WTForms field instance for this setting.

serialize(value: Any) str[source]

How this setting’s value is stored in the DB (JSON, not pickle).

class flaskbb.core.settings.definitions.StringSetting(key: str, value: Any, name: str, description: str, min: int | None = None, max: int | None = None)[source]
wtf_field()[source]

Return a WTForms field instance for this setting.

class flaskbb.core.settings.definitions.IntSetting(key: str, value: Any, name: str, description: str, min: int | None = None, max: int | None = None)[source]
wtf_field()[source]

Return a WTForms field instance for this setting.

class flaskbb.core.settings.definitions.BoolSetting(key: str, value: Any, name: str, description: str)[source]
wtf_field()[source]

Return a WTForms field instance for this setting.

class flaskbb.core.settings.definitions.SelectSetting(key: str, value: Any, name: str, description: str, choices: collections.abc.Callable[[], list[tuple[typing.Any, str]]], coerce: type = <class 'str'>)[source]
coerce

alias of str

wtf_field()[source]

Return a WTForms field instance for this setting.

class flaskbb.core.settings.definitions.SelectMultipleSetting(key: str, value: Any, name: str, description: str, choices: collections.abc.Callable[[], list[tuple[typing.Any, str]]], coerce: type = <class 'str'>)[source]
wtf_field()[source]

Return a WTForms field instance for this setting.

serialize(value)[source]

How this setting’s value is stored in the DB (JSON, not pickle).

Every definition takes key, value (the default), name (human readable label) and description. StringSetting and IntSetting additionally accept optional min/max bounds (string length or numeric range, validated via WTForms validators). SelectSetting and SelectMultipleSetting require a choices callable returning a list of (value, label) pairs, and accept an optional coerce type (defaults to str) to coerce the selected value(s).

Example:

from flaskbb.core.settings import BoolSetting, IntSetting, SettingGroup

SETTINGS = SettingGroup(
    key="my_plugin",
    name="My Plugin Settings",
    description="Settings for My Plugin.",
    settings=(
        BoolSetting(
            key="ENABLED",
            value=True,
            name="Enabled",
            description="Whether My Plugin is active.",
        ),
        IntSetting(
            key="RECENT_ITEMS",
            value=10,
            min=1,
            name="Number of Recent Items",
            description="How many items to show.",
        ),
    ),
)

Setting keys

A setting’s key only has to be unique within its own group - uniqueness is scoped to (group_key, key), not global. How a setting is exposed to the rest of the app (config-style access, form field names) differs between core and plugin settings, via flaskbb.core.settings.models.display_key():

  • Core settings (registered through flaskbb_load_internal_setting_groups) are exposed unprefixed: PROJECT_TITLE.

  • Plugin settings (registered through flaskbb_load_setting_groups) are exposed prefixed with their group key, uppercased: PORTAL_FORUM_IDS for a setting keyed FORUM_IDS in the portal group. This is what avoids collisions between plugins (and with core).

The registry

class flaskbb.core.settings.registry.SettingsRegistry[source]
core_groups()[source]

Groups loaded via flaskbb_load_internal_setting_groups

plugin_groups()[source]

Groups loaded via flaskbb_load_setting_groups

resolve_display_key(display_key: str) tuple[str, str][source]

Reverses a GROUPKEY_KEY display key (e.g. “PORTAL_FORUM_IDS”) back into its (group_key, raw_key) pair (e.g. (“portal”, “FORUM_IDS”)).

Raises KeyError if no registered (group_key, key) pair produces this display key.

load_from_internal(plugin_manager: FlaskBBPluginManager) None[source]

Call flaskbb_load_internal_setting_groups - core’s own hook. Only FlaskBB’s own hookimpls should implement this one.

load_from_plugins(plugin_manager: FlaskBBPluginManager) None[source]

Call flaskbb_load_setting_groups - the public hook that third-party plugins implement for their own SettingGroups.

Implementations may return a single SettingGroup or a list of them (mirrors the flaskbb_load_post_markdown_class convention of collecting-and-composing hook results rather than calling register_group directly from plugin code).

The module-level singleton flaskbb.core.settings.setting_registry is what every part of FlaskBB (admin forms, the settings model, the plugin registry) queries against - plugins never construct their own registry.

Storage and the Setting model

Note

For a full list of available methods, visit Setting Model.

class flaskbb.core.settings.models.Setting(key, value, group_key)[source]
classmethod as_dict() dict[str, Any][source]

Load and deserialize every setting value from the DB.

Core settings stay unprefixed (settings.PROJECT_TITLE), plugin settings are exposed prefixed with their group_key (settings.PORTAL_FORUM_IDS).

classmethod invalidate_cache()[source]

Invalidates this objects cached metadata.

classmethod update(group_key: str, settings: dict[str, Any], *, exclude: Iterable[str] = frozenset({'csrf_token'})) None[source]

Save a settings group’s form data to the DB and invalidate the cache in one step.

Parameters:
  • group_key – the SettingGroup.key whose settings are being saved (e.g. “general”, “portal”). Required because (group_key, key) together identify a setting.

  • settings – dict of {setting_key: new_value}, typically form.data from the WTForm built via build_form(group)

  • exclude – keys to skip. Defaults to just “csrf_token” because forms usually contain this key.

classmethod diff_group(group_key: str) SettingsDiff[source]

Compares a group’s currently registered SettingDefinitions against what actually has DB rows tagged with this group_key.

Returns:

SettingsDiff listing which definition keys have no DB row yet and which DB rows no longer match any definition .

classmethod prune_group(group_key: str) None[source]

Removes settings from a group that are no longer used. For example, removed from the SettingDefinitions file.

classmethod install_group(group_key: str) None[source]

Insert DB rows (using each definition’s default value) for any setting in this group that doesn’t have one yet.

Used when a plugin is installed for the first time, or when a new setting is added to an existing group via a fixture/plugin update.

classmethod remove_group(group_key: str) None[source]

Delete every DB row belonging to a settings group and invalidate the cache in one step.

Used when uninstalling a plugin - deletes only rows tagged with this group_key, so other groups settings are untouched.

Values are stored as JSON text (not PickleType) - deliberately, to avoid the arbitrary code execution risk of unpickling untrusted data, and because every setting value type (int, bool, str, list[str]) is JSON-safe anyway.

The whole settings table is cached as a flat dict via as_dict(), keyed by each setting’s display key. Any write path (update(), install_group(), prune_group(), remove_group()) invalidates this cache itself - never write Setting rows directly via the session and skip these methods, or the cache will go stale.

Reading and writing settings at runtime

Application code reads settings through the flaskbb_config proxy (flaskbb.core.settings.proxy.FlaskBBConfigProxy), which supports both attribute and dict-style access against the cached values from as_dict():

from flaskbb.core.settings import flaskbb_config

flaskbb_config.PROJECT_TITLE
flaskbb_config["PROJECT_TITLE"]

Writing through the proxy (flaskbb_config["KEY"] = value or flaskbb_config.update(...)) resolves the display key back to its (group_key, key) pair and delegates to update(), so plugin settings work the same way as core ones. Individual keys can’t be deleted through the proxy - a group’s settings are tied to its lifecycle, removed together via remove_group() (e.g. on plugin uninstall).