View .md

Plugin lifecycle and events

What the panel does when a FeatherPanel plugin is installed, updated or removed, which hooks your entry class gets, and how to listen to panel events from processEvents.

On this page

The entry class

The entry class implements App\Plugins\AppPlugin. It has three required static methods and one optional one.

MethodWhen it runs
processEvents(PluginEvents $event)When the backend boots: once per worker in the Docker image, once per request in classic mode. Register your listeners here.
pluginInstall()Once, after a fresh install.
pluginUpdate(?string $oldVersion, ?string $newVersion)Optional. After an update. A one-argument version also works. If the method does not exist, pluginInstall() runs instead.
pluginUninstall()Before the plugin’s files are removed.

Install

When you install a package from the marketplace, an upload or a URL, the panel:

  1. extracts the .fpa archive and checks conf.yml,
  2. on an update, saves the current plugin settings,
  3. copies the files to storage/addons/<identifier>,
  4. links Public/ to public/addons/<identifier> and Frontend/Components/ to public/components/<identifier> (copying them if a symlink is not possible),
  5. runs the SQL files in Migrations/; if one fails, the install stops with ADDON_MIGRATION_FAILED and shows the migration output,
  6. restores the saved settings on an update,
  7. calls pluginInstall() or pluginUpdate().

Uninstall

Uninstall in the admin area calls pluginUninstall(), removes the plugin folder and its public links, and marks the plugin as uninstalled. The panel does not drop your tables for you. If your plugin created tables or settings that should go away, remove them in pluginUninstall().

Listening to events

Panel code emits named events at fixed points: a user logs in, a server is created, a backup finishes and so on. Each event has a static method on a class in App\Plugins\Events\Events that returns its name.

<?php

namespace App\Addons\myplugin;

use App\App;
use App\Plugins\AppPlugin;
use App\Plugins\PluginEvents;
use App\Plugins\Events\Events\AppEvent;
use App\Plugins\Events\Events\AuthEvent;

class MyPlugin implements AppPlugin
{
    public static function processEvents(PluginEvents $event): void
    {
        $event->on(AuthEvent::onAuthLoginSuccess(), function (array $user): void {
            App::getInstance(true)->getLogger()->info('Login: ' . $user['username']);
        });

        $event->on(AppEvent::onRouterReady(), function ($router): void {
            // The route collection is ready.
        });
    }

    public static function pluginInstall(): void {}
    public static function pluginUninstall(): void {}
}

How arguments are passed: the panel emits an associative payload, for example ['user' => $userInfo], and calls each listener with its values in order. A payload with two keys means a listener with two parameters.

PluginEvents also has removeListener(), removeAllListeners() and listeners().

There are more than 400 events, grouped by area (auth, servers, files, backups, nodes, users, tickets, VDS, web hosting and others). The event reference lists each one with its name and payload.

Things to keep in mind

  • processEvents() runs when the backend boots, which is once per worker in the Docker image. Register listeners there, keep it fast and do not keep per-request state in it. See worker mode.
  • Listeners run inside the request that emitted the event. Long work belongs in a cron job.
  • A plugin whose entry class cannot be resolved is skipped and logged as Skipping plugin "<id>": entry class could not be resolved.

Frequently asked questions

What arguments does my event listener receive?

The values of the payload the panel emits, in order, as positional arguments. The event reference lists the payload keys for each event.

Does a failing pluginInstall hook stop the install?

No. Errors thrown by pluginInstall or pluginUpdate are logged and the install still completes. A failing migration does stop it.