Skip to content

Commit e313f96

Browse files
Merge pull request #173 from pelican-dev/boy132/plugin-docs
Plugin docs
2 parents 274ddb6 + 44dda12 commit e313f96

3 files changed

Lines changed: 348 additions & 1 deletion

File tree

docs/panel/advanced/plugins.mdx

Lines changed: 345 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,345 @@
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>

docusaurus.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,7 @@ const config: Config = {
108108
"ini",
109109
"sql",
110110
"yaml",
111+
"php",
111112
],
112113
},
113114
} satisfies Preset.ThemeConfig,
@@ -136,7 +137,7 @@ const config: Config = {
136137
],
137138
tailwindPlugin,
138139
],
139-
future:{
140+
future: {
140141
experimental_faster: true,
141142
v4: true
142143
}

sidebars.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const sidebars: SidebarsConfig = {
3131
'panel/advanced/mysql',
3232
'panel/advanced/artisan',
3333
'panel/advanced/docker',
34+
'panel/advanced/plugins',
3435
]
3536
}
3637
],

0 commit comments

Comments
 (0)