pelican-panel-plugins
Pelican Panel Plugins
Pelican is an open-source game server management panel built on Laravel + FilamentPHP. Plugins let you add functionality without touching core files.
⚠️ The plugin system is still in active development — some features may change.
Quick Reference
📖 Detailed Guides:
- FilamentPHP Patterns - Resources, pages, widgets, actions, relation managers
- Advanced Patterns - Models, services, routes, commands, events
- Complete Plugin Walkthrough - Step-by-step example
Scaffolding a New Plugin
Run inside the panel directory (/var/www/pelican by default):
php artisan p:plugin:make
This creates the basic structure with plugin.json, main plugin class, service provider, and config file.
Critical: The plugin folder name must exactly match the
idfield inplugin.json.
Plugin Structure
plugins/my-plugin/
├── plugin.json # Metadata and configuration
├── config/
│ └── my-plugin.php # Config values (use env vars)
├── database/
│ └── migrations/ # Auto-discovered migrations
├── lang/ # Translations (namespaced: my-plugin::strings.key)
├── resources/
│ └── views/ # Blade views (namespaced: my-plugin::view-name)
├── routes/ # Optional route files
└── src/ # App logic (PSR-4 autoloaded)
├── MyPlugin.php # Main plugin class
├── Filament/
│ ├── Admin/ # Admin panel components
│ │ ├── Pages/
│ │ ├── Resources/
│ │ └── Widgets/
│ ├── App/ # Server list panel
│ └── Server/ # Server management panel
├── Models/
├── Policies/ # Auto-discovered
├── Providers/ # Auto-discovered service providers
├── Console/Commands/ # Auto-discovered artisan commands
└── Http/
└── Controllers/
Everything in standard Laravel locations is auto-discovered: migrations, providers, commands, policies.
plugin.json
{
"id": "my-plugin",
"name": "My Plugin",
"author": "Your Name",
"version": "1.0.0",
"description": "Short description",
"category": "plugin",
"namespace": "MyName\\MyPlugin",
"class": "MyPlugin",
"panels": ["admin", "server"],
"panel_version": "^1.2.0",
"composer_packages": {
"vendor/package": "^1.0"
}
}
| Field | Required | Notes |
|---|---|---|
id |
✅ | Must match folder name |
namespace |
✅ | PHP namespace root (use \\ for backslashes) |
class |
✅ | Main class name (in src/) |
category |
✅ | plugin, theme, or language |
panels |
No | Array of panel IDs or omit for all panels |
panel_version |
No | Minimum panel version (e.g., ^1.2.0) |
composer_packages |
No | External dependencies |
Main Plugin Class
Located in src/{ClassName}.php:
namespace MyName\MyPlugin;
use Filament\Contracts\Plugin;
use Filament\Panel;
class MyPlugin implements Plugin
{
public function getId(): string
{
return 'my-plugin';
}
public function register(Panel $panel): void
{
$id = str($panel->getId())->title(); // "Admin", "App", "Server"
// Auto-discover Filament components
$panel->discoverPages(
plugin_path($this->getId(), "src/Filament/$id/Pages"),
"MyName\\MyPlugin\\Filament\\$id\\Pages"
);
$panel->discoverResources(
plugin_path($this->getId(), "src/Filament/$id/Resources"),
"MyName\\MyPlugin\\Filament\\$id\\Resources"
);
$panel->discoverWidgets(
plugin_path($this->getId(), "src/Filament/$id/Widgets"),
"MyName\\MyPlugin\\Filament\\$id\\Widgets"
);
}
public function boot(Panel $panel): void
{
//
}
}
The Three Filament Panels
| Panel ID | Area | Use Case |
|---|---|---|
admin |
Admin area | Full CRUD for resources, settings, management |
app |
Server list | Minimal UI (no nav by default) |
server |
Server management | Tenant-scoped (current server context) |
Enabling Navigation on app Panel
use App\Filament\App\Resources\Servers\ServerResource;
use App\Enums\CustomizationKey;
public function register(Panel $panel): void
{
parent::register($panel);
if ($panel->getId() === 'app') {
ServerResource::embedServerList();
$panel->navigation(true);
$panel->topbar(function () {
$nav = user()?->getCustomization(CustomizationKey::TopNavigation);
return in_array($nav, ['topbar', 'mixed', true], true);
});
$panel->clearCachedComponents();
}
}
Extending Core Resources
Call static methods on core classes inside a service provider's register():
use App\Filament\Admin\Resources\Users\UserResource;
use App\Filament\Admin\Resources\Servers\ServerResource;
use App\Models\Role;
public function register(): void
{
// Add a relation manager tab
ServerResource::registerCustomRelations(MyRelationManager::class);
// Register permissions
Role::registerCustomDefaultPermissions('myModel');
Role::registerCustomModelIcon('myModel', 'tabler-star');
}
Available customization traits (check app/Traits/Filament/ for all):
CanModifyResource- Relation managers, custom actionsCanCustomizePage- Widgets, header actionsCanModifyForm/CanModifyTable- Form/table hooks
Plugin Settings
Implement HasPluginSettings on your main class:
use App\Contracts\Plugins\HasPluginSettings;
use App\Traits\EnvironmentWriterTrait;
use Filament\Forms\Components\TextInput;
use Filament\Notifications\Notification;
class MyPlugin implements Plugin, HasPluginSettings
{
use EnvironmentWriterTrait;
public function getSettingsForm(): array
{
return [
TextInput::make('api_key')
->required()
->default(fn () => config('my-plugin.api_key')),
];
}
public function saveSettings(array $data): void
{
$this->writeToEnvironment([
'MY_PLUGIN_API_KEY' => $data['api_key'],
]);
Notification::make()->title('Settings saved')->success()->send();
}
}
Always prefix env vars with your plugin ID to avoid conflicts.
Permissions
Admin Role Permissions
In your service provider's register():
use App\Models\Role;
// Shorthand: registers viewList, view, create, update, delete
Role::registerCustomDefaultPermissions('myModel');
// Custom permissions
Role::registerCustomPermissions([
'myModel' => ['export', 'approve'],
'server' => ['customAction'], // extend existing model
]);
// Optional: icon for permission group
Role::registerCustomModelIcon('myModel', 'tabler-star');
Subuser Permissions
use App\Models\Subuser;
// New permission group
Subuser::registerCustomPermissions('myFeature', ['read', 'write'], 'tabler-bolt', false);
// Append to existing group
Subuser::registerCustomPermissions('console', ['myCustomAction']);
Routes
Create a RouteServiceProvider in src/Providers/:
use Illuminate\Foundation\Support\Providers\RouteServiceProvider;
use Illuminate\Support\Facades\Route;
class MyPluginRoutesProvider extends RouteServiceProvider
{
public function boot(): void
{
$this->routes(function () {
// Simple route
Route::get('/my-plugin/test', [TestController::class, 'index'])
->name('my-plugin.test');
// Load from file
Route::prefix('/my-plugin')
->group(plugin_path('my-plugin', 'routes/web.php'));
// Append to client API
Route::middleware(['api', 'client-api', 'throttle:api.client'])
->prefix('/api/client/servers/{server}')
->scopeBindings()
->group(plugin_path('my-plugin', 'routes/api-client.php'));
});
}
}
Models & Relationships
Add Relationship to Core Models
In your service provider's boot():
use App\Models\Server;
use MyPlugin\Models\Ticket;
public function boot(): void
{
Server::resolveRelationUsing('tickets', fn (Server $server) =>
$server->hasMany(Ticket::class, 'server_id', 'id')
);
}
Now $server->tickets works everywhere.
Policies
use App\Policies\DefaultAdminPolicies;
class MyModelPolicy
{
use DefaultAdminPolicies;
protected string $modelName = 'myModel';
}
This automatically checks admin role permissions based on the registered model name.
Translations
Place in lang/{locale}/ (e.g., lang/en/strings.php):
return [
'welcome' => 'Welcome',
'item' => 'Item|Items', // Pluralization
];
Usage:
trans('my-plugin::strings.welcome')
trans_choice('my-plugin::strings.item', 2) // "Items"
Views
Place in resources/views/:
view('my-plugin::my-view')
// → plugins/my-plugin/resources/views/my-view.blade.php
Common Patterns
FilamentPHP Components
See FilamentPHP Patterns for:
- Resources (CRUD interfaces)
- Pages (custom pages)
- Widgets (dashboard components)
- Relation Managers (manage related records)
- Custom Actions (reusable buttons)
- Form components (inputs, selects, toggles)
- Table columns and filters
Advanced Patterns
See Advanced Patterns for:
- Model events and hooks
- Enums with Filament interfaces
- Service classes
- HTTP controllers and API routes
- Artisan commands and scheduling
- HTTP macros for external APIs
- Database migrations
- Error handling
Complete Example
See Complete Plugin Walkthrough for a step-by-step guide building a "Server Notes" plugin.
Publishing a Plugin
- Open
plugin.jsonand remove themetablock (internal use only) - Zip the entire plugin folder
- Share the zip — users install via panel UI Import button or manually drop into
plugins/
Publish to the community:
- GitHub: pelican-dev/plugins
- Discord:
#pluginschannel at discord.gg/pelican-panel
Tips & Gotchas
- Namespace in plugin.json: Use
\\(double backslash) for namespace separators - Migration naming: Use numeric prefixes (
001_,002_) to control execution order - Environment variables: Always prefix with your plugin ID (e.g.,
MY_PLUGIN_*) - Panel context: Use
Filament::getTenant()to get current server in server panel - Auto-discovery: Service providers, commands, migrations, and policies are auto-discovered
- Relation managers: Must be registered on core resources via
registerCustomRelations()in service provider'sregister()method - Testing: Use
php artisan migrate:fresh --seedto reset and test migrations - Permissions: Register in service provider's
register(), notboot()
Getting Help
- Documentation: pelican.dev/docs/panel
- Discord: discord.gg/pelican-panel
- GitHub: github.com/pelican-dev/panel
- Example Plugins: github.com/pelican-dev/plugins
More from aaronflorey/agent-skills
amber-lang
Write, debug, and explain Amber code, the `amber` language that compiles `.ab` files to Bash. Use this skill when the user asks to write an Amber script, convert Bash to Amber, compile Amber to Bash, debug Amber syntax or type errors, or asks about Amber 0.5.1-alpha syntax, functions, types, error handling, the standard library, or the `amber` CLI.
26go-cobra
Write, scaffold, and debug Go CLI applications with `github.com/spf13/cobra`. Use this skill whenever the user mentions Cobra, `cobra.Command`, a Go command-line app, subcommands, persistent or local flags, required flags, argument validation, shell completions, generated docs, or wants to build or refactor a cobra-based CLI.
24laravel-actions
Write, scaffold, explain, and refactor code using the `lorisleiva/laravel-actions` package. Use this skill whenever the user mentions Laravel Actions, `AsAction`, `php artisan make:action`, action classes, converting a controller, job, listener, or command into an action, dispatching an action as a job, using an action as a controller or listener, or adding validation, authorization, testing, or mocking around an action.
24num30-config
Write, debug, and explain Go configuration code using `github.com/num30/config`. Use this skill when the user mentions `num30/config`, wants config structs, file plus env plus CLI flag loading, validation, config watching, precedence rules, or asks how to integrate the num30/config package into a Go application.
22go-viper
Write, debug, and explain Go configuration code with `github.com/spf13/viper`. Use this skill whenever the user mentions Viper, `viper`, config structs, reading config from files plus env vars plus flags, Cobra or `pflag` integration, unmarshaling into structs, env key replacers, config precedence, config watching, or a clean Viper bootstrap.
20mise
Configure and use `mise` for dev tool management, environment variables, and task running. Use this skill when the user mentions `mise`, `mise.toml`, `.mise.toml`, `mise use`, `mise install`, `mise run`, `mise x`, project tool versions like Node or Python, task definitions, env vars, hooks, backends, or asks how to configure mise in a project.
17