Debugging plugins
Find out why a FeatherPanel plugin does not load, a route returns 404 or a page stays empty. Log messages, the validate endpoint, worker restarts and a release checklist.
On this page
Where to look
| Tool | What it tells you |
|---|---|
storage/logs/App.fplog | Everything the plugin loader, the router and your own logger calls write. In Docker: /var/www/html/storage/logs/App.fplog in the backend container, on the featherpanel_logs volume. |
| Logs in the admin area (System group) | The same log in the browser. Needs admin.root. |
| Admin > Plugins > your plugin | Whether the plugin is loaded, unmet dependencies and missing required settings. |
GET /api/admin/plugin-manager/{identifier}/validate | A checklist, see below. Developer mode and admin.root only. |
/var/log/cron.log in the backend container | Output of the cron runner, including your cron classes. |
| Browser dev tools | Errors from Frontend/index.js, your iframe pages and failed /api/addons/... calls. |
Write your own log lines with the panel logger:
use App\App;
$logger = App::getInstance(true)->getLogger();
$logger->info('myplugin: sync started');
$logger->warning('myplugin: remote API slow');
$logger->error('myplugin: sync failed: ' . $e->getMessage(), false);
error() and critical() also send the message to the panel’s error telemetry unless the second argument is
false. debug() only writes when APP_DEBUG is on, which it is not in release builds, so use info() instead.
Developer mode and the debug console
Turn on developer mode (the app_developer_mode setting) in the admin settings. It unlocks:
- Admin > Dev > Plugins: create plugins from templates, edit their manifest and settings, and add migrations, cron jobs, commands and public files with Create file,
- plugin export,
- the validate endpoint,
- the panel debug console: type
##icanhasfeatherpanelin the global search. It has network probes, benchmarks, a log tail for admins, a debug snapshot you can copy, and links to the developer docs and the dev plugin page.
The validate endpoint
GET /api/admin/plugin-manager/{identifier}/validate returns:
| Field | True when |
|---|---|
config_valid | conf.yml passes the same checks the loader uses. |
dependencies_met | Every dependencies entry is satisfied. |
required_configs_set | Every key in requiredConfigs has a stored value. |
identifier_valid | The identifier only uses letters, digits and _. |
flags_valid | Every flag is a known flag. |
files_exist | conf.yml and <plugin.name>.php exist in the plugin root. |
overall_valid | All of the above. |
Log messages from the plugin loader
The loader checks each folder in storage/addons/ in this order and stops at the first problem:
| Log message | Cause and fix |
|---|---|
Invalid plugin identifier: <folder> | The folder name has characters other than letters, digits and _. Rename it. |
Plugin config is empty for: <id> | conf.yml is missing, empty or does not parse. |
Missing key for plugin: <id> key: <key> then Invalid config for plugin: <id> | A required plugin: key is missing. Add it, even if the value is an empty list. |
Invalid type for plugin: <id> key: <key> | A key has the wrong type, for example author as a string instead of a list. |
Invalid flags for plugin: <id> | flags contains an unknown value. |
Skipping plugin "<folder>": conf.yml identifier "<x>" does not match folder name | Rename the folder or fix plugin.identifier. |
Plugin <id> has unmet dependencies! | A php=, php-ext=, composer= or plugin= entry is not satisfied. The admin plugin page lists which. |
Skipping plugin "<id>": entry class could not be resolved. Ensure plugin.name, the PHP class name, and namespace App\Addons\<id> all match. | The loader looks for App\Addons\<id>\<plugin.name>. Check the namespace line, the class name, the file name and that the class implements App\Plugins\AppPlugin. |
Failed to process plugin event | Your processEvents() threw. The message is not logged, so wrap its body in try/catch and log the exception yourself while you debug. |
Failed to load plugin routes from <file>: <message> | A route file threw while loading. Only that file is skipped. |
Skipping addon route file whose name matches another addon identifier | A file in your Routes/ has the same name as another installed plugin. Rename it. |
Missing required config '<key>' for plugin: <id> | A key from requiredConfigs has no stored value. The plugin still loads. |
Errors when installing a package
Uploading or installing an .fpa checks the entry class before anything is copied. The API answers 422 with:
| Error code | Meaning |
|---|---|
ADDON_INVALID | No conf.yml at the root of the archive. The files must not sit inside a folder. |
ADDON_CONF_PARSE_FAILED | conf.yml is not valid YAML. |
ADDON_IDENTIFIER_INVALID | The identifier in conf.yml is missing or invalid. |
ADDON_ENTRY_INVALID | The entry class check failed. data.errors lists every problem, for example Entry class namespace "..." does not match required "App\Addons\<id>". |
ADDON_MIGRATION_FAILED | A file in Migrations/ failed. The response includes the migration output. |
Common problems
A route returns API_ROUTE_NOT_FOUND
- The file is not under
Routes/, does not end in.php, or does notreturn function (RouteCollection $routes). - The route file threw while loading. Search the log for
Failed to load plugin routes. - The API runs in worker mode and the worker started before you added the file. Click Resync on the plugin page
or restart the backend container. Any successful admin change under
/api/admin/pluginsmakes the workers restart.
PHP changes do not show up
In worker mode, which the Docker image uses by default, each worker keeps your classes in memory until it restarts.
Click Resync, save a plugin setting, or restart the backend container. To develop without restarts, set
FRANKENPHP_WORKER=false on the backend container so every request loads the code again. See
Middleware and router hooks.
Frontend/index.js or index.css changes do not show up
The panel joins every plugin’s index.js (and index.css) and caches the result for two minutes, and the browser may
keep it for another minute. Wait, then hard reload. Each plugin’s script is wrapped in its own function, so a variable
you expect on window must be assigned to window explicitly. If your script uses window.FeatherPanel, wait until
it exists, as the Starter template does.
A sidebar page is empty or shows the wrong file
componentis a path insideFrontend/Components/. The panel loads/components/<identifier>/<component>.- After copying files by hand, click Resync so
public/components/<identifier>points at your folder. - Open the component URL directly in the browser to see the real error.
A cron job never runs
- The class must be
App\Addons\<identifier>\Cron\<file name>. The cron log showsClass ... not foundotherwise. - The interval unit must be upper case:
5M, not5m. - An earlier PHP
Error(notException) in any job stops the whole run. Check the cron log for a fatal error. - Delete
storage/caches/cron/<id>.fptjto force the next run.
A setting is always empty
default in conf.yml is only a placeholder in the form. Seed values in pluginInstall() or fall back in code. See
Settings.
Checklist before you release
conf.ymlhas every required key, a bumpedversion, and an identifier that matches the folder.- The validate endpoint reports
overall_valid: true. - The entry class is the only PHP file in the plugin root.
- Route names and paths are prefixed with your identifier.
- Every value from a request reaches SQL through prepared statements.
- Listeners and
processEvents()catch their own exceptions. - Nothing user-specific is kept in static properties.
- Cron classes use
App\Addons\<identifier>\Cronand upper-case intervals. - Migrations use
IF NOT EXISTSand table names start withfeatherpanel_<identifier>_. pluginUninstall()removes the tables and settings the plugin created.- The plugin installs from an
.fpaon a clean panel. See Packaging and publishing.
Related
Frequently asked questions
The application log is backend/storage/logs/App.fplog, which is /var/www/html/storage/logs/App.fplog in the Docker backend container. Admins with admin.root can also read it on the Logs page in the admin area.
Usually the conf.yml is invalid, the identifier does not match the folder, a dependency is missing or the entry class name, file name and namespace do not match. The log names the exact reason; search it for your identifier.