Install the Package
composer require coolsam/filament-modules
Ensure nwidart/laravel-modules is installed (auto-installed via dependency).
Configure Laravel Modules
Follow Laravel Modules docs to set up module autoloading in composer.json:
"extra": {
"merge-plugin": {
"include": ["Modules/*/composer.json"]
}
}
Run:
php artisan modules:install
Register the ModulesPlugin
In your AdminPanelProvider (or relevant panel provider):
use Coolsam\Modules\ModulesPlugin;
public function panel(Panel $panel): Panel {
return $panel
->plugin(ModulesPlugin::make());
}
Create a Module
php artisan module:make MyModule
Initialize Filament in the Module
php artisan module:filament:install MyModule
Follow prompts to configure clusters/panels.
Generate a resource inside your module:
php artisan module:filament:resource Post --model=App\Models\Post --cluster=MyModule
This creates a resource under Modules/MyModule/app/Filament/Clusters/MyModule/Resources/PostResource.php.
Cluster-Based Workflow:
Use clusters to group related Filament components (e.g., PostsCluster, UsersCluster).
php artisan module:filament:cluster PostsCluster --module=MyModule
Place resources/pages/widgets inside Modules/MyModule/app/Filament/Clusters/PostsCluster/.
Panel Isolation:
Create standalone panels for modules (e.g., CMSPanel, AnalyticsPanel):
php artisan module:filament:panel CMSPanel --module=MyModule
Access via the main panel’s navigation (configurable in config/filament-modules.php).
Auto-Registered Plugins:
Enable auto-register-plugins: true in config/filament-modules.php to auto-load all module plugins.
Override MyModulePlugin.php to customize plugin behavior (e.g., add settings):
public function getPluggable(): array {
return [
FilamentSettings::make('settings', Settings::class),
];
}
Conditional Loading:
Use shouldRegister() in MyModulePlugin to conditionally load plugins:
public function shouldRegister(): bool {
return config('filament-modules.enable_my_module');
}
Reusable Widgets/Pages: Create widgets/pages in one module and reuse them in others by:
composer require my-vendor/my-module).use Modules\MyModule\Widgets\AnalyticsWidget;
class ExtendedAnalyticsWidget extends AnalyticsWidget {
// Override methods as needed
}
Cluster Inheritance: Extend clusters to share navigation or layouts:
class ExtendedCluster extends Cluster {
public function getNavigationItems(): array {
return array_merge(
parent::getNavigationItems(),
[/* custom items */]
);
}
}
Module-Specific Policies:
Use CanAccessTrait in resources/pages:
use Coolsam\Modules\CanAccessTrait;
class PostResource extends Resource {
use CanAccessTrait;
public static function canAccess(): bool {
return auth()->user()->hasRole('editor');
}
}
Policy Integration: Attach policies to module models:
// In MyModuleServiceProvider
Gate::define('view-post', function (User $user, Post $post) {
return $user->isAdmin() || $post->user_id === $user->id;
});
Dynamic Module Loading:
Disable auto-register-plugins and manually register plugins in boot():
public function boot() {
if ($this->shouldLoadModule()) {
$this->app->register(\Modules\MyModule\Providers\MyModuleServiceProvider::class);
}
}
Cluster Navigation:
Customize cluster navigation in MyModuleCluster.php:
public function getNavigationItems(): array {
return [
NavigationItem::make('Posts')
->icon('heroicon-o-document-text')
->url(fn () => fn() => route('filament.my-module.posts.index')),
];
}
Module Isolation:
Test modules in isolation using Laravel’s --module flag:
php artisan test --module=MyModule
Mock dependencies in MyModuleServiceProvider:
public function register() {
$this->app->bind(\Modules\MyModule\Contracts\PostRepository::class, function () {
return new MockPostRepository();
});
}
Plugin Testing: Test plugins by registering them in a temporary panel:
public function test_plugin_registration() {
$panel = Panel::make();
$panel->plugin(ModulesPlugin::make());
$this->assertCount(1, $panel->getPlugins());
}
Module Publishing: Publish modules as Composer packages:
composer config repositories.my-module vcs https://github.com/my-vendor/my-module.git
composer require my-vendor/my-module
Ensure composer.json includes:
"extra": {
"merge-plugin": {
"include": ["vendor/my-vendor/my-module/Modules/MyModule/composer.json"]
}
}
Environment-Specific Modules: Load modules conditionally based on environment:
// In AppServiceProvider
if (app()->environment('production')) {
$this->app->register(\Modules\Analytics\Providers\AnalyticsServiceProvider::class);
}
Autoloading Issues:
merge-plugin is configured in composer.json and run:
composer dump-autoload
Cluster Navigation Conflicts:
getNavigationItems() in MyModuleCluster.php and ensure URLs are correct:
->url(fn () => fn() => route('filament.my-module.cluster.resource.index'))
Plugin Registration Order:
AdminPanelProvider with dependencies first:
->plugin(MyBasePlugin::make())
->plugin(MyModulePlugin::make())
Model Binding in Resources:
Resource not binding to the correct model.protected static ?string $model = \Modules\MyModule\Models\Post::class;
Panel Isolation Gaps:
panels.group is set in config/filament-modules.php and panels are registered:
->panel(MyModulePanel::make())
Log Module Loading:
Add debug logs in ModulesPlugin:
public function getId(): string {
\Log::debug('Registering ModulesPlugin');
return 'modules-plugin';
}
Check Configuration: Dump the config to verify settings:
\Log::debug(config('filament-modules'));
Verify Routes:
Use php artisan route:list to check if module routes are registered. Missing routes often indicate:
route() definitions in resources/pages.Test Module Isolation: Temporarily disable other modules to isolate issues:
php artisan modules:disable OtherModule
Custom Module Commands:
Extend the package’s commands (e.g., MakeFilamentResourceCommand) by publishing and overriding:
php artisan vendor:publish --tag="filament-modules-commands"
Modify app/Console/Commands/MakeFilamentResource.php.
Dynamic Plugin Registration:
Implement shouldRegister() in custom plugins to add
How can I help you explore Laravel packages today?