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
| Key | Type | Required | Notes |
|---|---|---|---|
name | string | yes | Must equal the entry class name. |
identifier | string | yes | Letters, digits and _, no spaces. Also the folder name and the namespace segment. |
description | string | yes | Shown in the admin plugin list. |
flags | list | yes | See below. |
version | string | yes | Your plugin version. |
target | string | yes | The panel generation the plugin is built for, v2 for current plugins. |
author | list | yes | Names. |
icon | string | yes | URL of the icon shown in the admin area. |
dependencies | list | yes | See below. May be empty. |
requiredConfigs | list | yes | Setting keys that must be filled in. May be empty. |
plugin_cloud_id | string or integer | no | The plugin’s id on the FeatherCloud marketplace. |
minimum_panel_version, maximum_panel_version | string | no | Version range the plugin supports. Must be a valid version string. The marketplace install check uses the range stored on the marketplace entry. |
mixins | map | no | Mixin 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.
| Flag | Meaning |
|---|---|
hasEvents | The entry class subscribes to events in processEvents(). Set it on every plugin. |
hasInstallScript, hasRemovalScript, hasUpdateScript | The plugin ships install, removal or update logic. |
developerIgnoreInstallScript, developerEscalateInstallScript, userEscalateInstallScript | Control how install scripts run in developer and normal mode. |
Dependencies
Each entry is type=value:
| Entry | Checks |
|---|---|
php=8.3 | The PHP version. |
php-ext=pdo | A loaded PHP extension. |
composer=vendor/package | A Composer package available to the panel. |
plugin=otherplugin | Another 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.
| Field | Notes |
|---|---|
name | Setting key. Your code reads it with this name. Fields without a name are ignored. |
display_name | Label in the form. Also used in validation error messages. |
type | text, 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. |
description | Help text under the field. |
required | See v1.5.2 and v1.5.3. |
validation | A map of extra rules, see Validation rules. {} means no rules. |
default | See v1.5.2 and v1.5.3. |
v1.5.2 and v1.5.3
The settings form changed in v1.5.3 (PR #365).
| Key | v1.5.2 and earlier | v1.5.3 and later |
|---|---|---|
required | Shows 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”. |
default | Shown 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". |
validation | Ignored. | Checked on every save through the admin API. |
type | Only 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.
| Rule | Type | Meaning |
|---|---|---|
regex | string | PCRE 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”. |
min | number | For type: number, or validation.type integer or number: the smallest allowed value. For every other type: the shortest allowed length in characters. |
max | number | Like min, for the largest value or the longest length. |
type | string | Extra format check on top of the field type: string, integer (whole number, optional leading -), number, boolean, email or url. |
message | string | Replaces 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.mixinsis read too; when both declare the same id, the top-level entry wins,- a
mixinsvalue that is not a map, or an entry whose options are not a map, makesconf.ymlinvalid, - mixins are not attached in the cron runner or
php cli, - your code reads them with
PluginProcessor::getMixin($identifier, $mixinId)orPluginProcessor::getMixins($identifier).
Related
Frequently asked questions
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.
Call App\Plugins\PluginSettings::getSetting('<identifier>', '<key>'). Values are stored as strings, so a boolean comes back as 'true' or 'false'.