filament-plugin

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Filament Plugin Skill — Modular Laravel Architecture

Filament插件技能——Laravel模块化架构

Concept

概念

Each plugin lives in
plugins/{name}/
and is registered as a local Composer package via a path repository in
composer.json
. The main project consumes it like any other Composer package, keeping full independence and modularity.
每个插件位于
plugins/{name}/
目录中,并通过
composer.json
中的路径仓库注册为本地Composer包。主项目像使用其他Composer包一样使用它,保持完全的独立性和模块化。

Required plugin structure

必需的插件结构

plugins/
└── {name}/
    ├── composer.json
    ├── src/
    │   ├── {Name}ServiceProvider.php
    │   ├── {Name}Plugin.php          <- Filament Plugin class
    │   └── Commands/
    │       └── Install{Name}Command.php
    ├── resources/
    │   ├── views/
    │   └── lang/
    ├── config/
    │   └── {name}.php
    └── database/
        └── migrations/
plugins/
└── {name}/
    ├── composer.json
    ├── src/
    │   ├── {Name}ServiceProvider.php
    │   ├── {Name}Plugin.php          <- Filament Plugin类
    │   └── Commands/
    │       └── Install{Name}Command.php
    ├── resources/
    │   ├── views/
    │   └── lang/
    ├── config/
    │   └── {name}.php
    └── database/
        └── migrations/

Step 1 — Create
plugins/{name}/composer.json

步骤1 — 创建
plugins/{name}/composer.json

json
{
    "name": "app/{name}",
    "description": "Modular {Name} plugin for Filament",
    "type": "library",
    "require": {
        "filament/filament": "^3.0"
    },
    "autoload": {
        "psr-4": {
            "App\\Plugins\\{Name}\\": "src/"
        }
    },
    "extra": {
        "laravel": {
            "providers": [
                "App\\Plugins\\{Name}\\{Name}ServiceProvider"
            ]
        }
    },
    "minimum-stability": "dev",
    "prefer-stable": true
}
json
{
    "name": "app/{name}",
    "description": "Modular {Name} plugin for Filament",
    "type": "library",
    "require": {
        "filament/filament": "^3.0"
    },
    "autoload": {
        "psr-4": {
            "App\\Plugins\\{Name}\\": "src/"
        }
    },
    "extra": {
        "laravel": {
            "providers": [
                "App\\Plugins\\{Name}\\{Name}ServiceProvider"
            ]
        }
    },
    "minimum-stability": "dev",
    "prefer-stable": true
}

Step 2 — Register in the project root
composer.json

步骤2 — 在项目根目录的
composer.json
中注册

Add the local path repository and require the package:
json
{
    "repositories": [
        {
            "type": "path",
            "url": "plugins/{name}",
            "options": {
                "symlink": true
            }
        }
    ],
    "require": {
        "app/{name}": "*"
    }
}
Then run:
bash
composer require app/{name} --no-scripts
composer dump-autoload
WARNING: If other path plugins already exist in repositories, only ADD the new entry — do NOT replace existing ones.
添加本地路径仓库并引入该包:
json
{
    "repositories": [
        {
            "type": "path",
            "url": "plugins/{name}",
            "options": {
                "symlink": true
            }
        }
    ],
    "require": {
        "app/{name}": "*"
    }
}
然后运行:
bash
composer require app/{name} --no-scripts
composer dump-autoload
注意:如果仓库中已存在其他路径插件,只需添加新条目——不要替换现有插件。

Step 3 — ServiceProvider

步骤3 — ServiceProvider

File:
plugins/{name}/src/{Name}ServiceProvider.php
php
<?php

namespace App\Plugins\{Name};

use Illuminate\Support\ServiceProvider;

class {Name}ServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->mergeConfigFrom(
            __DIR__ . '/../config/{name}.php',
            '{name}'
        );
    }

    public function boot(): void
    {
        $this->loadViewsFrom(__DIR__ . '/../resources/views', '{name}');
        $this->loadTranslationsFrom(__DIR__ . '/../resources/lang', '{name}');

        // Migrations are loaded directly from the plugin so `migrate` works
        // out of the box. The Install command copies them physically (Step 5).
        $this->loadMigrationsFrom(__DIR__ . '/../database/migrations');

        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__ . '/../database/migrations' => database_path('migrations'),
            ], '{name}-migrations');

            $this->publishes([
                __DIR__ . '/../config/{name}.php' => config_path('{name}.php'),
            ], '{name}-config');

            $this->publishes([
                __DIR__ . '/../resources/views' => resource_path('views/vendor/{name}'),
            ], '{name}-views');

            $this->commands([
                \App\Plugins\{Name}\Commands\Install{Name}Command::class,
            ]);
        }
    }
}
文件:
plugins/{name}/src/{Name}ServiceProvider.php
php
<?php

namespace App\Plugins\{Name};

use Illuminate\Support\ServiceProvider;

class {Name}ServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        $this->mergeConfigFrom(
            __DIR__ . '/../config/{name}.php',
            '{name}'
        );
    }

    public function boot(): void
    {
        $this->loadViewsFrom(__DIR__ . '/../resources/views', '{name}');
        $this->loadTranslationsFrom(__DIR__ . '/../resources/lang', '{name}');

        // 迁移文件直接从插件加载,因此`migrate`命令可直接生效
        // 安装命令会将它们物理复制到项目中(步骤5)。
        $this->loadMigrationsFrom(__DIR__ . '/../database/migrations');

        if ($this->app->runningInConsole()) {
            $this->publishes([
                __DIR__ . '/../database/migrations' => database_path('migrations'),
            ], '{name}-migrations');

            $this->publishes([
                __DIR__ . '/../config/{name}.php' => config_path('{name}.php'),
            ], '{name}-config');

            $this->publishes([
                __DIR__ . '/../resources/views' => resource_path('views/vendor/{name}'),
            ], '{name}-views');

            $this->commands([
                \App\Plugins\{Name}\Commands\Install{Name}Command::class,
            ]);
        }
    }
}

Step 4 — Filament Plugin Class

步骤4 — Filament插件类

File:
plugins/{name}/src/{Name}Plugin.php
php
<?php

namespace App\Plugins\{Name};

use Filament\Contracts\Plugin;
use Filament\Panel;

class {Name}Plugin implements Plugin
{
    public function getId(): string
    {
        return '{name}';
    }

    public function register(Panel $panel): void
    {
        // Register resources, pages, widgets, etc.
        // Example:
        // $panel->resources([
        //     \App\Plugins\{Name}\Resources\{Name}Resource::class,
        // ]);
    }

    public function boot(Panel $panel): void
    {
        //
    }

    public static function make(): static
    {
        return app(static::class);
    }
}
Important: Remind the user to register the plugin in their Panel Provider:
php
// app/Providers/Filament/AdminPanelProvider.php
->plugins([
    \App\Plugins\{Name}\{Name}Plugin::make(),
])
文件:
plugins/{name}/src/{Name}Plugin.php
php
<?php

namespace App\Plugins\{Name};

use Filament\Contracts\Plugin;
use Filament\Panel;

class {Name}Plugin implements Plugin
{
    public function getId(): string
    {
        return '{name}';
    }

    public function register(Panel $panel): void
    {
        // 注册资源、页面、小组件等
        // 示例:
        // $panel->resources([
        //     \App\Plugins\{Name}\Resources\{Name}Resource::class,
        // ]);
    }

    public function boot(Panel $panel): void
    {
        //
    }

    public static function make(): static
    {
        return app(static::class);
    }
}
重要提示:提醒用户在Panel Provider中注册插件:
php
// app/Providers/Filament/AdminPanelProvider.php
->plugins([
    \App\Plugins\{Name}\{Name}Plugin::make(),
])

Step 5 — Install Command (with migration copy)

步骤5 — 安装命令(含迁移文件复制)

File:
plugins/{name}/src/Commands/Install{Name}Command.php
php
<?php

namespace App\Plugins\{Name}\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;

class Install{Name}Command extends Command
{
    protected $signature   = '{name}:install {--force : Overwrite existing files}';
    protected $description = 'Install the {Name} plugin and publish its assets';

    public function handle(): int
    {
        $this->info('Installing {Name} plugin...');

        // 1. Publish config
        $this->callSilently('vendor:publish', [
            '--tag'   => '{name}-config',
            '--force' => $this->option('force'),
        ]);
        $this->line('  [OK] Config published');

        // 2. Copy migrations into the project
        $this->publishMigrations();

        // 3. Publish views (optional)
        if ($this->confirm('Would you like to publish the views for customization?', false)) {
            $this->callSilently('vendor:publish', [
                '--tag'   => '{name}-views',
                '--force' => $this->option('force'),
            ]);
            $this->line('  [OK] Views published');
        }

        $this->newLine();
        $this->info('{Name} plugin installed successfully.');
        $this->line('  -> Remember to add <comment>{Name}Plugin::make()</comment> to your Panel Provider.');
        $this->line('  -> Run <comment>php artisan migrate</comment> to apply the migrations.');

        return self::SUCCESS;
    }

    protected function publishMigrations(): void
    {
        $source      = __DIR__ . '/../../database/migrations';
        $destination = database_path('migrations');

        if (! File::isDirectory($source)) {
            $this->line('  [i] No migrations found in this plugin');
            return;
        }

        $files   = File::files($source);
        $copied  = 0;
        $skipped = 0;

        foreach ($files as $file) {
            $target = $destination . '/' . $file->getFilename();

            if (File::exists($target) && ! $this->option('force')) {
                $skipped++;
                continue;
            }

            File::copy($file->getPathname(), $target);
            $copied++;
        }

        if ($copied > 0) {
            $this->line("  [OK] {$copied} migration(s) copied to database/migrations");
        }
        if ($skipped > 0) {
            $this->line("  [i] {$skipped} migration(s) skipped (already exist — use --force to overwrite)");
        }
    }
}
文件:
plugins/{name}/src/Commands/Install{Name}Command.php
php
<?php

namespace App\Plugins\{Name}\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\File;

class Install{Name}Command extends Command
{
    protected $signature   = '{name}:install {--force : Overwrite existing files}';
    protected $description = 'Install the {Name} plugin and publish its assets';

    public function handle(): int
    {
        $this->info('Installing {Name} plugin...');

        // 1. 发布配置文件
        $this->callSilently('vendor:publish', [
            '--tag'   => '{name}-config',
            '--force' => $this->option('force'),
        ]);
        $this->line('  [OK] 配置文件已发布');

        // 2. 将迁移文件复制到项目中
        $this->publishMigrations();

        // 3. 发布视图文件(可选)
        if ($this->confirm('是否要发布视图文件以便自定义?', false)) {
            $this->callSilently('vendor:publish', [
                '--tag'   => '{name}-views',
                '--force' => $this->option('force'),
            ]);
            $this->line('  [OK] 视图文件已发布');
        }

        $this->newLine();
        $this->info('{Name}插件安装成功。');
        $this->line('  -> 请记得将<comment>{Name}Plugin::make()</comment>添加到你的Panel Provider中。');
        $this->line('  -> 运行<comment>php artisan migrate</comment>以执行迁移。');

        return self::SUCCESS;
    }

    protected function publishMigrations(): void
    {
        $source      = __DIR__ . '/../../database/migrations';
        $destination = database_path('migrations');

        if (! File::isDirectory($source)) {
            $this->line('  [i] 此插件中未找到迁移文件');
            return;
        }

        $files   = File::files($source);
        $copied  = 0;
        $skipped = 0;

        foreach ($files as $file) {
            $target = $destination . '/' . $file->getFilename();

            if (File::exists($target) && ! $this->option('force')) {
                $skipped++;
                continue;
            }

            File::copy($file->getPathname(), $target);
            $copied++;
        }

        if ($copied > 0) {
            $this->line("  [OK] 已将{$copied}个迁移文件复制到database/migrations");
        }
        if ($skipped > 0) {
            $this->line("  [i] 已跳过{$skipped}个迁移文件(已存在——使用--force参数可覆盖)");
        }
    }
}

Step 6 — Minimal supporting files

步骤6 — 基础支持文件

plugins/{name}/config/{name}.php

plugins/{name}/config/{name}.php

php
<?php

return [
    'enabled' => true,
];
php
<?php

return [
    'enabled' => true,
];

plugins/{name}/database/migrations/
(if applicable)

plugins/{name}/database/migrations/
(如有需要)

Name migrations with a timestamp + description:
2024_01_01_000000_create_{name}_table.php
迁移文件命名格式为:时间戳 + 描述:
2024_01_01_000000_create_{name}_table.php

Naming conventions

命名规范

TokenExample (name = "invoicing")
{name}
invoicing
{Name}
Invoicing
namespace
App\Plugins\Invoicing
package
app/invoicing
directory
plugins/invoicing/
command
invoicing:install
For compound names (e.g. "purchase order"):
  • {name}
    ->
    purchase-order
    (kebab-case for Composer and directory)
  • {Name}
    ->
    PurchaseOrder
    (PascalCase for classes)
  • namespace ->
    App\Plugins\PurchaseOrder
占位符示例(name = "invoicing")
{name}
invoicing
{Name}
Invoicing
命名空间
App\Plugins\Invoicing
包名
app/invoicing
目录
plugins/invoicing/
命令
invoicing:install
对于复合名称(如“purchase order”):
  • {name}
    ->
    purchase-order
    (Composer和目录使用短横线命名法)
  • {Name}
    ->
    PurchaseOrder
    (类使用大驼峰命名法)
  • 命名空间 ->
    App\Plugins\PurchaseOrder

Delivery checklist

交付检查清单

After creating a plugin, always show this summary to the user:
[OK] Plugin {Name} created at plugins/{name}/
[OK] Registered in composer.json (path repository)
[OK] ServiceProvider with loadMigrationsFrom()
[OK] Install command: php artisan {name}:install

Next steps:
   1. composer require app/{name}
   2. php artisan {name}:install
   3. Add {Name}Plugin::make() to your Panel Provider
   4. php artisan migrate
创建插件后,务必向用户展示以下摘要:
[OK] 插件{Name}已创建于plugins/{name}/
[OK] 已在composer.json中注册(路径仓库)
[OK] 已创建包含loadMigrationsFrom()的ServiceProvider
[OK] 已创建安装命令:php artisan {name}:install

后续步骤:
   1. composer require app/{name}
   2. php artisan {name}:install
   3. 将{Name}Plugin::make()添加到你的Panel Provider中
   4. php artisan migrate

Additional reference

额外参考

For complex plugins with Filament Resources, Pages, and Widgets, see:
references/filament-components.md
For advanced migration handling and versioning, see:
references/migrations-advanced.md
如需创建包含Filament资源、页面和小组件的复杂插件,请查看:
references/filament-components.md
如需了解高级迁移处理和版本控制,请查看:
references/migrations-advanced.md