返回 Skill 列表
extension
分类: 开发与工程无需 API Key

pelican-panel-plugins

编写、构建、解释和调试Pelican游戏面板的插件。每当用户想要创建Pelican插件、向Pelican面板添加功能、为Pelican构建FilamentPHP资源或页面、注册自定义权限、添加插件设置、编写插件路由,或者询问任何关于Pelican插件开发的问题时——即使他们只是说“我想给Pelican添加点东西”或“我该如何扩展Pelican”,都使用这项技能。

person作者: jakexiaohubgithub

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:

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 id field in plugin.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 actions
  • CanCustomizePage - Widgets, header actions
  • CanModifyForm / 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

  1. Open plugin.json and remove the meta block (internal use only)
  2. Zip the entire plugin folder
  3. Share the zip — users install via panel UI Import button or manually drop into plugins/

Publish to the community:

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's register() method
  • Testing: Use php artisan migrate:fresh --seed to reset and test migrations
  • Permissions: Register in service provider's register(), not boot()

Getting Help