View .md

Plugin manifest (conf.yml)

Every key of a FeatherPanel plugin conf.yml: flags, dependencies, panel version limits, required settings and the config schema behind the settings form.

On this page

Every plugin has a conf.yml at the root of its folder. The top-level plugin: block describes the plugin; the optional config: block describes the settings an admin can change.

plugin:
  name: DemoShowcase
  identifier: demoshowcase
  description: 'Shows how pages, widgets and settings work'
  flags:
    - hasEvents
  version: 1.0.0
  target: v2
  author:
    - Your Name
  icon: 'https://example.com/icon.png'
  requiredConfigs: []
  dependencies:
    - php=8.3
    - php-ext=pdo
  minimum_panel_version: 1.0.0
config:
  - name: hide_dashboard_banner
    display_name: 'Hide dashboard banner widget'
    type: boolean
    description: 'When true, the dashboard banner widget is hidden.'
    required: false
    validation: {}
    default: 'false'

The plugin block

KeyTypeRequiredNotes
namestringyesMust equal the entry class name.
identifierstringyesLetters, digits and _, no spaces. Also the folder name and the namespace segment.
descriptionstringyesShown in the admin plugin list.
flagslistyesSee below.
versionstringyesYour plugin version.
targetstringyesThe panel generation the plugin is built for, v2 for current plugins.
authorlistyesNames.
iconstringyesURL of the icon shown in the admin area.
dependencieslistyesSee below. May be empty.
requiredConfigslistyesSetting keys that must be filled in. May be empty.
plugin_cloud_idstring or integernoThe plugin’s id on the FeatherCloud marketplace.
minimum_panel_version, maximum_panel_versionstringnoVersion range the plugin supports. Must be a valid version string. The marketplace install check uses the range stored on the marketplace entry.
mixinsmapnoMixin ids to attach, with an options map each. Up to v1.5.2 nothing registers mixin classes, so every entry only logs No mixin registered with identifier: <id>. Registration is fixed in v1.5.3 (PR #365). See Mixins.

Flags

flags may contain only these values; any other value makes the manifest invalid.

FlagMeaning
hasEventsThe entry class subscribes to events in processEvents(). Set it on every plugin.
hasInstallScript, hasRemovalScript, hasUpdateScriptThe plugin ships install, removal or update logic.
developerIgnoreInstallScript, developerEscalateInstallScript, userEscalateInstallScriptControl how install scripts run in developer and normal mode.

Dependencies

Each entry is type=value:

EntryChecks
php=8.3The PHP version.
php-ext=pdoA loaded PHP extension.
composer=vendor/packageA Composer package available to the panel.
plugin=otherpluginAnother installed plugin.

A plugin whose dependencies are missing is not loaded, and the admin plugin page lists what is missing.

The config block

config: is a list of fields. The admin plugin page turns it into a form on the Settings tab.

FieldNotes
nameSetting key. Your code reads it with this name. Fields without a name are ignored.
display_nameLabel in the form. Also used in validation error messages.
typetext, textarea, url, email, password, number, boolean or file. boolean renders a checkbox, password a masked input, number a number input; anything else is a text input.
descriptionHelp text under the field.
requiredSee v1.5.2 and v1.5.3.
validationA map of extra rules, see Validation rules. {} means no rules.
defaultSee v1.5.2 and v1.5.3.

v1.5.2 and v1.5.3

The settings form changed in v1.5.3 (PR #365).

Keyv1.5.2 and earlierv1.5.3 and later
requiredShows a “required” badge. Empty values save without complaint.An empty or whitespace-only value is rejected by the API and the form. A required field without a value also marks the plugin “Not configured”.
defaultShown as the input placeholder. Never saved.Saved when the key is not stored yet: after install, after every update and when an admin opens the settings page. Existing values are never overwritten. true/false are stored as "true"/"false".
validationIgnored.Checked on every save through the admin API.
typeOnly picks the input.Also checked: number must be numeric, boolean must be true, false, 1 or 0, email must be an email address, url must be an http or https URL.

On v1.5.2, seed defaults in pluginInstall() and validate values in your own code. That code keeps working after the update.

Validation rules

The validation map is read from v1.5.3 (on v1.5.2 it is accepted and ignored, so you can add it now). Every rule is optional and only runs on a non-empty value; an empty optional field is always accepted.

RuleTypeMeaning
regexstringPCRE pattern with delimiters, such as '/^\d+$/' or '#^https://#i'. The value must match. A pattern that does not compile fails every save with “has an invalid validation pattern in conf.yml”.
minnumberFor type: number, or validation.type integer or number: the smallest allowed value. For every other type: the shortest allowed length in characters.
maxnumberLike min, for the largest value or the longest length.
typestringExtra format check on top of the field type: string, integer (whole number, optional leading -), number, boolean, email or url.
messagestringReplaces the generated error text when any rule fails.
config:
  - name: api_key
    display_name: 'API key'
    type: password
    required: true
    default: ''
    validation:
      regex: '/^[A-Za-z0-9$._-]{20,}$/'
      min: 20
      max: 120
      message: 'Paste the API key from the provider console.'
  - name: query_port
    display_name: 'Query port'
    type: text
    required: false
    default: '25565'
    validation:
      type: integer
      min: 1
      max: 65535

A value that fails is rejected by POST /api/admin/plugins/{identifier}/settings/set with status 400 and error_code: PLUGIN_SETTING_INVALID. The text is in message, and data holds the key and the list of errors, for example {"key": "query_port", "errors": ["Query port must be at most 65535."]}. The developer bulk endpoint PUT/PATCH /api/admin/plugin-manager/{identifier}/settings applies the same rules. Keys that are not declared in config: are not checked, and neither is PluginSettings::setSetting() called from PHP.

Values are saved per plugin and read with PluginSettings:

use App\Plugins\PluginSettings;

$hide = PluginSettings::getSetting('demoshowcase', 'hide_dashboard_banner') === 'true';

Keys you list in requiredConfigs that have no saved value are reported as missing on the admin plugin page and logged. The plugin still loads. From v1.5.3 an empty value also counts as missing, config: fields with required: true count the same way, and the admin API adds plugin.configured: false while anything is missing (fixed in v1.5.3, PR #365). Settings covers the form, the API and the events in detail. Widgets and pages can also be hidden from a setting without code, see the hidden rule on Widget slots.

Mixins

A mixin is a PHP class that implements App\Plugins\Mixins\AppMixin. A plugin asks for mixins by id, with an options map that is passed to the mixin’s initialize():

mixins:
  RateLimiter:
    per_minute: 30

On v1.5.2 and earlier nothing registers mixin classes, so this has no effect beyond a No mixin registered with identifier log line per entry. Mixin registration is fixed in v1.5.3 (PR #365). From then on:

  • the web kernel registers every class in backend/app/Plugins/Mixins/Custom/ once, before plugins load, and attaches the declared mixins while each plugin loads,
  • the panel ships no built-in mixins, so an id only resolves if such a class has been added to the panel,
  • plugin.mixins is read too; when both declare the same id, the top-level entry wins,
  • a mixins value that is not a map, or an entry whose options are not a map, makes conf.yml invalid,
  • mixins are not attached in the cron runner or php cli,
  • your code reads them with PluginProcessor::getMixin($identifier, $mixinId) or PluginProcessor::getMixins($identifier).

Frequently asked questions

What happens if conf.yml is invalid?

The panel logs a warning that names the missing or wrong key and does not load the plugin. Nothing else in the panel is affected.

How does my plugin read its settings?

Call App\Plugins\PluginSettings::getSetting('<identifier>', '<key>'). Values are stored as strings, so a boolean comes back as 'true' or 'false'.