# 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.

Source: https://featherpanel.com/docs/plugins/manifest/

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.

```yaml
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](https://github.com/MythicalLTD/FeatherPanel/pull/365)). See [Mixins](#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](#v152-and-v153). |
| `validation` | A map of extra rules, see [Validation rules](#validation-rules). `{}` means no rules. |
| `default` | See [v1.5.2 and v1.5.3](#v152-and-v153). |

### v1.5.2 and v1.5.3

The settings form changed in v1.5.3 ([PR #365](https://github.com/MythicalLTD/FeatherPanel/pull/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. |

```yaml
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`:

```php
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](https://github.com/MythicalLTD/FeatherPanel/pull/365)). [Settings](https://featherpanel.com/docs/plugins/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](https://featherpanel.com/docs/plugins/widgets/).

## 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()`:

```yaml
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](https://github.com/MythicalLTD/FeatherPanel/pull/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)`.

## Related

- [Plugin structure](https://featherpanel.com/docs/plugins/getting-started/)
- [Lifecycle and events](https://featherpanel.com/docs/plugins/lifecycle-and-events/)
- [Packaging and publishing](https://featherpanel.com/docs/plugins/packaging-and-publishing/)

## 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'.
