theavuthnhel/modules
Laravel package for building modular applications with self-contained modules. Includes module generation, enable/disable management, service providers, routes, migrations, views, config, and Artisan commands for organizing large codebases into reusable components.
This package is a bridge, not a framework: it teaches Filament how to find and register resources, pages, widgets, and relation managers that live inside nWidart/laravel-modules modules. Treat it as plumbing you set up once and then use every day through the normal Filament workflow.
Prerequisites and install
composer require nwidart/laravel-modules filament/filament
composer require theavuthnhel/modules
php artisan vendor:publish --provider="Theavuthnhel\Modules\ModulesServiceProvider" # verify tag with `php artisan vendor:publish --list`
Note the package name vs. repo: you're pulling theavuthnhel/modules, which tracks the savannabits/filament-modules lineage. Pin an explicit version in composer.json and read the fork's README for any drift before relying on it in production.
Where to look first
config/filament-modules.php after publishing — this is the control surface. Look for the module discovery paths, namespace mapping, and the on/off switches.app/Filament/ directory. That's the convention the package scans: Modules/Blog/app/Filament/Resources, .../Pages, .../Widgets.app/Providers/Filament/AdminPanelProvider.php) to confirm the plugin is registered and discovery runs for that panel.First use case
php artisan module:make Blog
php artisan module:make Filament/Resources/PostResource Blog # or the package's generator; run `php artisan list | grep -i filament` to confirm the exact signature
Then open the panel. If the resource appears in navigation, discovery is wired correctly. That single round-trip is the smoke test for the whole integration.
Module-local Filament components
Keep everything Filament-related inside the module: resources, pages, widgets, relation managers, custom form/table components, policies. The module becomes a vertical slice — you can hand it to another team or delete it cleanly. The package's job is making Filament's reflection-based discovery traverse those namespaces instead of only App\Filament.
Navigation grouping
Use protected static ?string $navigationGroup on resources/pages to group by module. This is the single biggest quality-of-life win: one group per module, ordered consistently, and the admin nav becomes a map of the domain rather than a flat list. Set groups explicitly rather than relying on alphabetical ordering, and centralize the group names (enum or constants) so they don't drift.
Multi-panel setups
If you have separate panels (admin, staff, partner), scope modules to panels in the config rather than filtering at the resource level. The config file typically lets you declare which modules (or paths) apply to which panel. Prefer this over public static function shouldRegisterNavigation(): bool checks scattered through code, which are hard to audit.
Shared base classes
Once you have more than ~3 modules, promote a base ModuleResource (or trait) into a shared module or app/Filament that module resources extend. Common things to centralize:
getNavigationGroup() derived from the module name->persistSearchInSession()getModuleName() helper derived from the resource's namespaceThis is the pattern that keeps 15 modules from becoming 15 divergent styles.
Custom pages and widgets
Module-scoped custom pages are the natural home for dashboards and cross-cutting reports (e.g., Modules/Reports/app/Filament/Pages/RevenueOverview.php). Register widgets per module and let the panel pull them in. Watch that widget canView() respects the module's feature flags — a widget visible to a user who can't see the underlying data is worse than no widget.
Integration tips
Livewire::test() against the module's resource pages; module-scoped code is usually easier to test than app-scoped code because the dependency surface is smaller.Discovery silently finds nothing. Almost always one of: wrong namespace in the config, composer dump-autoload not run after generating the module, or the resource class not matching the expected Filament/Resources path. Debug with php artisan filament:cache-components (and its clear counterpart) plus php artisan route:list | grep -i yourmodule. If routes exist but nav doesn't, it's shouldRegisterNavigation/canView, not discovery.
Caches fight you. Filament caches component discovery and Laravel caches config. After changing the modules config or moving a class, run php artisan optimize:clear and php artisan filament:optimize-clear. In CI, always run with a cleared cache; stale-discovery failures are the #1 false-negative in module test suites.
Fork maintenance risk. theavuthnhel/modules with 0 dependents and a recent release is a thin maintenance surface. Before adopting: confirm it supports your Filament major version (v3 vs v4 discovery internals differ substantially), check open issues on the upstream repo, and be prepared to vendor or patch. If you need long-term stability, isolate the integration behind your own service provider so swapping the package out later is a one-file change.
Config publish tag drift. The provider's publish tags can change between versions. Run php artisan vendor:publish --list and grep for filament and module rather than trusting a copy-pasted tag. If the config never appears, the provider may be auto-discovered — check bootstrap/providers.php / config/app.php.
Policies and authorization live in the module too. Filament resolves policies by model class. If a module's model is Modules\Blog\Models\Post, the policy must be Modules\Blog\Policies\PostPolicy and registered — either via convention plus Gate::policy() in the module provider, or through Laravel's auto-discovery if namespaces line up. Mismatches here produce "everyone can see everything" or "nobody can see anything," both equally confusing.
Assets and Vite. Module-specific CSS/JS (custom themes, dashboard widgets with charts) need their own Vite entry points or a shared build. Don't assume the module's assets are bundled automatically — verify in the built manifest, or you'll ship a panel that looks fine locally and breaks in production.
Extension points to lean on
Plugin interface for anything cross-cutting (global header actions, render hooks) — implement it in one place rather than per module.Debugging order of operations. When something "doesn't show up": (1) is the module enabled and autoloaded? (2) does the class match the discovery path? (3) is discovery cached? (4) does the panel register the plugin? (5) is canView/canAccess returning false? That checklist resolves the overwhelming majority of issues in under a minute.
How can I help you explore Laravel packages today?