|
| 1 | +import Admonition from '@theme/Admonition'; |
| 2 | +import Tabs from '@theme/Tabs'; |
| 3 | +import TabItem from '@theme/TabItem'; |
| 4 | + |
| 5 | +# Plugins |
| 6 | + |
| 7 | +Plugins allow you to add new functionality to the panel without modifying any panel core files. |
| 8 | + |
| 9 | +<Admonition type="warning"> |
| 10 | + The plugin system is still in development and features might be missing! |
| 11 | +</Admonition> |
| 12 | + |
| 13 | +## Install a Plugin |
| 14 | + |
| 15 | +<Tabs> |
| 16 | + <TabItem value="frontend" label="Via Panel Frontend"> |
| 17 | + Use the Import Button on the plugin list to upload & install a plugin. |
| 18 | + </TabItem> |
| 19 | + <TabItem value="manually" label="Manually"> |
| 20 | + Move the plugin folder to `plugins` inside your panel directory (`/var/www/pelican/plugins` by default). Then run `php artisan p:plugin:install` inside your panel directory and select the new plugin. |
| 21 | + </TabItem> |
| 22 | +</Tabs> |
| 23 | + |
| 24 | +<Admonition type="info" title="Need some plugins?"> |
| 25 | + Check out our [Plugins repo](https://github.com/pelican-dev/plugins)! It contains free and open source plugins created by the Pelican team and the community. |
| 26 | + We are also working on adding a plugin marketplace to the [Hub](https://hub.pelican.dev). |
| 27 | +</Admonition> |
| 28 | + |
| 29 | +## Create a Plugin |
| 30 | + |
| 31 | +<Admonition type="danger"> |
| 32 | + You **need** at least basic [PHP](https://php.net), [Laravel](https://laravel.com) and [Filament](https://filamentphp.com) knowledge to create plugins! |
| 33 | +</Admonition> |
| 34 | + |
| 35 | +Run `php artisan p:plugin:make` inside your panel directory (`/var/www/pelican` by default) to create the basic files and folders for your new plugin. This will create the `plugin.json`, the main plugin file, a config file and a service provider. |
| 36 | + |
| 37 | +<Admonition type="info" title="Need help?"> |
| 38 | + Visit our [Discord](https://discord.gg/pelican-panel) if you need any help when creating plugins! |
| 39 | +</Admonition> |
| 40 | + |
| 41 | +### Plugin Structure |
| 42 | + |
| 43 | +The two most important files of your plugin are the `plugin.json` (which contains general information like name, version etc.) and the main plugin file. (where you can load Filament resources, pages etc.) |
| 44 | +While the `plugin.json` is in the root of your plugin folder, the main plugin file is in `src`. |
| 45 | + |
| 46 | +In general, the plugin folder tries to mimic the structure of a normal Laravel/FilamentPHP project. For example, translations go into `lang` and migrations are in `database/migrations`. |
| 47 | +Everything is auto-discovered and service providers, artisan commands, migrations etc. are automatically registered. |
| 48 | + |
| 49 | +The only exception to a normal Laravel project is the `app` folder, it is called `src` instead. Besides that, is still follows the same sub-structure, e.g. models go in `src/Models` and service providers are in `src/Providers`. |
| 50 | + |
| 51 | +<Admonition type="info"> |
| 52 | + The plugin id and the plugin root folder name need to match! |
| 53 | +</Admonition> |
| 54 | + |
| 55 | +#### plugin.json |
| 56 | + |
| 57 | +Most of the fields inside the `plugin.json` are generated when running the [artisan command](#create-a-plugin). |
| 58 | + |
| 59 | +| Field | Required | Description | |
| 60 | +| ----------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- | |
| 61 | +| id | Yes | Unique identifier of the plugin. Has to match with the plugin root folder name! | |
| 62 | +| name | Yes | Display name of the plugin. | |
| 63 | +| author | Yes | Display name of the plugin creator. | |
| 64 | +| version | No | Version of the plugin. Ideally, this should follow [semantic versioning](https://semver.org/). When not set it will assume version 1.0.0. | |
| 65 | +| description | No | Short description of the plugin. | |
| 66 | +| category | Yes | Either `plugin`, `theme` or `language`. | |
| 67 | +| update_url | No | URL that will be fetched when checking for updates. See [below](#update-url) for more information. | |
| 68 | +| namespace | Yes | The php namespace for the plugin files. | |
| 69 | +| class | Yes | The name of the plugin main class. | |
| 70 | +| panels | No | Array of [FilamentPHP panel](#filament-panels) ids the plugin should be loaded on. When not set the plugin will be loaded on all panels. | |
| 71 | +| panel_version | No | The panel version required for the plugin. See [below](#panel-version) for more information. | |
| 72 | +| composer_packages | No | Array of additional composer packages. Key is the package name and value is the version. See [below](#additional-composer-dependencies) for more information. | |
| 73 | + |
| 74 | +<Admonition type="info"> |
| 75 | + You can ignore the `meta` fields. Those are used internally to keep track of the plugin status and load order. |
| 76 | +</Admonition> |
| 77 | + |
| 78 | +#### Update URL |
| 79 | + |
| 80 | +In your `plugin.json` you can specify an `update_url` that is fetched when checking for plugin updates. |
| 81 | +When not set, there will be no update checking for the plugin. |
| 82 | + |
| 83 | +It needs to point directly to a json file with the following format: |
| 84 | + |
| 85 | +```json title="update.json" |
| 86 | +{ |
| 87 | + "panel_version_1": { |
| 88 | + "version": "1.0.0", |
| 89 | + "download_url": "..." |
| 90 | + }, |
| 91 | + "panel_version_2": { |
| 92 | + "version": "1.1.0", |
| 93 | + "download_url": "..." |
| 94 | + } |
| 95 | +} |
| 96 | +``` |
| 97 | + |
| 98 | +If you don't have panel-version-specific plugin versions, you can also use `*` as panel version wildcard: |
| 99 | + |
| 100 | +```json title="update.json" |
| 101 | +{ |
| 102 | + "*": { |
| 103 | + "version": "1.1.0", |
| 104 | + "download_url": "..." |
| 105 | + } |
| 106 | +} |
| 107 | +``` |
| 108 | + |
| 109 | +You can also mix the wildcard with real panel versions. This way the wildcard will be used as fallback when the users panel version isn't specified in the update json. |
| 110 | + |
| 111 | +Example: |
| 112 | + |
| 113 | +```json title="update.json" |
| 114 | +{ |
| 115 | + "panel_version_1": { |
| 116 | + "version": "1.0.0", |
| 117 | + "download_url": "..." |
| 118 | + }, |
| 119 | + "*": { |
| 120 | + "version": "1.1.0", |
| 121 | + "download_url": "..." |
| 122 | + } |
| 123 | +} |
| 124 | +``` |
| 125 | + |
| 126 | +#### Additional composer dependencies |
| 127 | + |
| 128 | +If you need additional composer packages as dependency for your plugin, you can add the package name to the `composer_packages` array in your `plugin.json`. |
| 129 | + |
| 130 | +Example: |
| 131 | + |
| 132 | +```json |
| 133 | +"composer_packages": { |
| 134 | + "package/one": "^1.0", |
| 135 | + "package/two": "^1.0" |
| 136 | +} |
| 137 | +``` |
| 138 | + |
| 139 | +#### Panel version |
| 140 | + |
| 141 | +In your `plugin.json` you can specify a `panel_version` that is used for compatibility checks. |
| 142 | +When not set, the plugin will be loaded regardless of the panel version. |
| 143 | + |
| 144 | +You can add a `^` in front of the version to make it a minimum constraint instead of a strict one, e.g. `1.2.0` means "_only_ version 1.2.0" while `^1.2.0` means "version 1.2.0 _or higher_". |
| 145 | + |
| 146 | +### Filament Resources |
| 147 | + |
| 148 | +To simply register Filament resources, pages or widgets you can add the following snippet to the `register` function in your main plugin file: |
| 149 | + |
| 150 | +```php {3-5} |
| 151 | +$id = str($panel->getId())->title(); |
| 152 | + |
| 153 | +$panel->discoverPages(plugin_path($this->getId(), "src/Filament/$id/Pages"), "<namespace>\\Filament\\$id\\Pages"); |
| 154 | +$panel->discoverResources(plugin_path($this->getId(), "src/Filament/$id/Resources"), "<namespace>\\Filament\\$id\\Resources"); |
| 155 | +$panel->discoverWidgets(plugin_path($this->getId(), "src/Filament/$id/Widgets"), "<namespace>\\Filament\\$id\\Widgets"); |
| 156 | +``` |
| 157 | + |
| 158 | +Make sure to replace `<namespace>` with your actual namespace. |
| 159 | + |
| 160 | +### Filament Panels |
| 161 | + |
| 162 | +By default there are three Filament panels: |
| 163 | + |
| 164 | +1. `admin` (Admin Area) |
| 165 | +2. `app` (Server list) |
| 166 | +3. `server` (Client Area, with `Server` model as [tenant](https://filamentphp.com/docs/4.x/users/tenancy)) |
| 167 | + |
| 168 | +#### App panel |
| 169 | + |
| 170 | +The `app` panel is solely used for the server list and has no navigation. |
| 171 | +If you want to expand the `app` panel, you can re-enable the navigation by adding the following snippet to the `register` function in your main plugin file: |
| 172 | + |
| 173 | +```php |
| 174 | +if ($panel->getId() === 'app') { |
| 175 | + // Optional: "Embed" the server list. This will add a Servers navigation item for the server list. |
| 176 | + // ServerResource::embedServerList(); |
| 177 | + |
| 178 | + // Re-enable navigation |
| 179 | + $panel->navigation(true); |
| 180 | + |
| 181 | + // By default the topbar is always used. With proper navigation we want to conditionally enable it based on the users customization |
| 182 | + $panel->topbar(function () { |
| 183 | + $navigationType = user()?->getCustomization(CustomizationKey::TopNavigation); |
| 184 | + |
| 185 | + return $navigationType === 'topbar' || $navigationType === 'mixed' || $navigationType === true; |
| 186 | + }); |
| 187 | + |
| 188 | + // Rebuild component cache |
| 189 | + $panel->clearCachedComponents(); |
| 190 | +} |
| 191 | +``` |
| 192 | + |
| 193 | +### Translations |
| 194 | + |
| 195 | +Normally, translations will be registered under the namespace of your plugin. |
| 196 | +This means you need to prefix translation keys with your plugin id when using `trans()`, e.g. `trans('myplugin::strings.test')` would look for `test` inside `plugins/myplugin/lang/strings.php`. |
| 197 | + |
| 198 | +If your plugin adds new default languages to the panel you need to set the plugin category to `language`. This way translations will be registered without any namespace. |
| 199 | + |
| 200 | +### Views |
| 201 | + |
| 202 | +Views are automatically registered under the namespace of your plugin. |
| 203 | +This means you need to prefix view-strings with your plugin id, e.g. `myplugin::my-view` would look for `plugins/myplugin/resources/views/my-view.blade.php`. |
| 204 | + |
| 205 | +### Seeder |
| 206 | + |
| 207 | +If present, a seeder with the name of your plugin will be automatically registered. It needs to be named `<plugin name>Seeder` (e.g. `MyPluginSeeder`) and put inside your plugins `database/Seeder` folder, e.g. `plugins/myplugin/database/Seeder/MyPluginSeeder.php`. |
| 208 | +This seeder will be automatically called when a user installs the plugin or when seeders are run in general, e.g. when using `php artisan db:seed` or `php artisan migrate --seed`. |
| 209 | + |
| 210 | +### Plugin Settings |
| 211 | + |
| 212 | +Your main plugin class can implement the `HasPluginSettings` interface to conveniently add a settings page to your plugin. |
| 213 | +The settings page can be accessed on the plugin list on the panel. Your plugin needs to provide the form and how the data should be saved. |
| 214 | + |
| 215 | +If you want to save the settings in the `.env` file you can use the `EnvironmentWriterTrait`. Example: |
| 216 | + |
| 217 | +```php |
| 218 | +use EnvironmentWriterTrait; |
| 219 | + |
| 220 | +public function getSettingsForm(): array |
| 221 | +{ |
| 222 | + return [ |
| 223 | + TextInput::make('test_input') |
| 224 | + ->required() |
| 225 | + ->default(fn () => config('myplugin.test_input')), |
| 226 | + Toggle::make('test_toggle') |
| 227 | + ->inline(false) |
| 228 | + ->default(fn () => config('myplugin.test_toggle')), |
| 229 | + ]; |
| 230 | +} |
| 231 | + |
| 232 | +public function saveSettings(array $data): void |
| 233 | +{ |
| 234 | + $this->writeToEnvironment([ |
| 235 | + 'MYPLUGIN_TEST_INPUT' => $data['test_input'], |
| 236 | + 'MYPLUGIN_TEST_TOGGLE' => $data['test_toggle'], |
| 237 | + ]); |
| 238 | + |
| 239 | + Notification::make() |
| 240 | + ->title('Settings saved') |
| 241 | + ->success() |
| 242 | + ->send(); |
| 243 | +} |
| 244 | +``` |
| 245 | + |
| 246 | +<Admonition type="info" title="Naming Conventions"> |
| 247 | + When adding new environment variables make sure to prefix them with your plugin id, e.g. `MYPLUGIN_TEST_INPUT` and not just `TEST_INPUT`. |
| 248 | +</Admonition> |
| 249 | + |
| 250 | +### Modify existing resources |
| 251 | + |
| 252 | +Most of the default panel resources and pages have static methods to register custom classes or to change existing ones. You can call these static methods inside the `register` function of a service provider. |
| 253 | + |
| 254 | +Examples: |
| 255 | + |
| 256 | +```php title="MyPluginProvider.php" |
| 257 | +public function register(): void |
| 258 | +{ |
| 259 | + // Add new relation manager to UserResource |
| 260 | + UserResource::registerCustomRelations(MyCustomRelationManager::class); |
| 261 | + |
| 262 | + // Add new widget to Console page |
| 263 | + Console::registerCustomWidgets(ConsoleWidgetPosition::AboveConsole, [MyCustomWidget::class]); |
| 264 | + |
| 265 | + // Add new header action to Server list page |
| 266 | + ListServers::registerCustomHeaderActions(HeaderActionPosition::Before, MyCustomAction::make()); |
| 267 | +} |
| 268 | +``` |
| 269 | + |
| 270 | +For more information you can check the ["CanModify" and "CanCustomize" traits in `./app/Traits/Filament`](https://github.com/pelican-dev/panel/tree/main/app/Traits/Filament). |
| 271 | + |
| 272 | +### Add new permissions |
| 273 | + |
| 274 | +#### Admin Roles |
| 275 | + |
| 276 | +To add new admin role permissions you can use `Role::registerCustomPermissions` and `Role::registerCustomDefaultPermissions`. |
| 277 | + |
| 278 | +You can call these static methods inside the `register` function of a service provider. Examples: |
| 279 | + |
| 280 | +```php title="MyPluginProvider.php" |
| 281 | +public function register(): void |
| 282 | +{ |
| 283 | + // Register default permissions: viewList, view, create, update, delete |
| 284 | + Role::registerCustomDefaultPermissions('myModel'); |
| 285 | + |
| 286 | + // Register custom permissions |
| 287 | + Role::registerCustomPermissions([ |
| 288 | + 'myModel' => [ |
| 289 | + 'customPerm1', |
| 290 | + 'customPerm2' |
| 291 | + ] |
| 292 | + ]); |
| 293 | + |
| 294 | + // Optionally, register an icon for a custom model |
| 295 | + Role::registerCustomModelIcon('myModel', 'tabler-star'); |
| 296 | + |
| 297 | + // You can also add new permissions to existing models |
| 298 | + Role::registerCustomPermissions([ |
| 299 | + 'server' => [ |
| 300 | + 'custom', |
| 301 | + ] |
| 302 | + ]); |
| 303 | +} |
| 304 | +``` |
| 305 | + |
| 306 | +#### Subuser |
| 307 | + |
| 308 | +To add new subuser permissions you can use `Subuser::registerCustomPermissions`. |
| 309 | + |
| 310 | +You can call this static method inside the `register` function of a service provider. Examples: |
| 311 | + |
| 312 | +```php title="MyPluginProvider.php" |
| 313 | +public function register(): void |
| 314 | +{ |
| 315 | + // Register new subuser permissions (call with icon and hidden param) |
| 316 | + Subuser::registerCustomPermissions('custom', ['customPerm1', 'customPerm2'], 'tabler-star', false); |
| 317 | + |
| 318 | + // You can also add new permissions to existing permission groups (call without icon and hidden param) |
| 319 | + Subuser::registerCustomPermissions('console', ['custom']); |
| 320 | +} |
| 321 | +``` |
| 322 | + |
| 323 | +## Publish a Plugin |
| 324 | + |
| 325 | +<Admonition type="danger"> |
| 326 | + While Pelican is still in beta, please do **not** sell plugins. Also, if possible, please make them open source. |
| 327 | + During the beta we still add new features and change existing ones. So keeping your plugins open helps us to improve the plugin system! |
| 328 | +</Admonition> |
| 329 | + |
| 330 | +To publish a plugin you only need to do two things: |
| 331 | + |
| 332 | +1. Open your `plugin.json` and remove the `meta` part. |
| 333 | +2. Zip up your whole plugin folder. |
| 334 | + |
| 335 | +And that's it! Now you can take the zip file and share it with the world! |
| 336 | + |
| 337 | +<Admonition type="info" title="Marketplace"> |
| 338 | + We are currently working on adding a plugin marketplace to the [Hub](https://hub.pelican.dev). |
| 339 | + Until then you can publish your plugins on our [plugins repo](https://github.com/pelican-dev/plugins). You can also share them in the `#plugins` channel in our [Discord](https://discord.gg/pelican-panel). |
| 340 | +</Admonition> |
| 341 | + |
| 342 | +<Admonition type="info" title="License"> |
| 343 | + You can license your plugin code under whatever license you want. You do not have to use the same license as the panel! |
| 344 | + You also don't have to open source your plugin code. But if you do want to open source it we recommend [MIT](https://choosealicense.com/licenses/mit) or [GPL v3](https://choosealicense.com/licenses/gpl-3.0) as license. |
| 345 | +</Admonition> |
0 commit comments