Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Modules Laravel Package

coolsam/modules

Filament Modules v5 wraps nwidart/laravel-modules to turn each Laravel module into an autonomous Filament plugin with its own resources, pages, widgets, and clusters. Register ModulesPlugin once and it auto-discovers modules. Supports Laravel 11–13 and Filament 4–5.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install the two-layer stack in the right order. coolsam/modules is a wrapper, not a standalone package — it will pull in nwidart/laravel-modules (^11 on Laravel 11, ^12 on Laravel 12, ^13 on Laravel 13), but you must configure that package before the wrapper does anything useful.

composer require coolsam/modules

2. Wire up module autoloading in composer.json — this is the step people skip, and everything downstream fails without it:

"extra": {
    "merge-plugin": {
        "include": ["Modules/*/composer.json"]
    }
}

Then composer dump-autoload. Confirm config/modules.php exists and that the Modules namespace maps where you expect (php artisan module:list).

3. Run the installer and register the plugin.

php artisan modules:install
# or just: php artisan vendor:publish --tag="modules-config"
// app/Providers/Filament/AdminPanelProvider.php
use Coolsam\Modules\ModulesPlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugin(ModulesPlugin::make());
}

That single registration is the whole point of the package: it discovers every module's generated *Plugin class and mounts it, so you never touch AdminPanelProvider again when a new module appears.

4. Where to look first.

  • config/filament-modules.php — the mode key is the single most consequential setting. Read the ConfigMode enum before you build anything.
  • Your module's generated app/Filament/<Module>Plugin.php — this is the class ModulesPlugin auto-discovers. If a module's resources don't show up, this file and its register()/boot() bodies are your first suspect.
  • The generated cluster class (if you chose clusters) — it defines the navigation group label/icon for the whole module.

5. First use case — a 10-minute vertical slice:

php artisan module:make Blog
php artisan module:filament:install Blog   # interactive: cluster? default cluster?
php artisan module:filament:resource       # interactive: pick Blog, pick model

Refresh the admin panel. A "Blog" nav group with your resource inside it, with zero edits to the host app's panel provider. That's the value proposition, and it holds up.


Implementation Patterns

Choose your mode deliberately, then stop thinking about it

filament-modules.mode accepts plugins / panels / both (default both). Pick early, because it changes your file layout and navigation model:

Mode What you get Good for
plugins Each module mounts its resources/pages/widgets into the host panel Monolithic admin, modules are just code organization
panels Each module gets its own panel, linked from the main panel under panels.group Hard isolation, per-module auth/theme/branding
both Both of the above You will get duplicate navigation entries unless you're careful — see Gotchas

With panels, the config keys panels.group, panels.group-icon, panels.open-in-new-tab, and panels.group-sort do all the navigation work. open-in-new-tab is worth enabling for module panels — it makes the "these are separate apps" mental model obvious to users.

Cluster-per-module as the default layout

The cluster is what makes a module feel like a feature rather than a folder. Set clusters.enabled: true and clusters.use-top-navigation: true, then let module:filament:install generate Clusters/<Module>/ with Pages, Resources, Widgets beneath it. Every subsequent module:filament:resource prompt offers to place the file in the cluster — always say yes.

use-top-navigation promotes clusters to the top bar and pushes their pages into a side sub-nav. This is the highest-leverage single flag in the package for UX: it turns a flat 40-item sidebar into a small number of top-level domains.

Generate, don't hand-write

All generators share three aliases each, so pick one and put it in your shell history:

php artisan module:filament:resource      # module:make:filament-resource / module:filament:make-resource
php artisan module:filament:page
php artisan module:filament:widget
php artisan module:filament:cluster
php artisan module:filament:plugin
php artisan module:filament:panel
php artisan module:filament:theme         # WIP

The generators know about clusters, panels, and the module namespace. Hand-rolling a resource in a module and getting the namespace, cluster membership, and plugin registration right is three separate chances to be wrong — the generator gets all three for free.

Nested plugins inside a module

A module's MyModulePlugin is the entry point, but module:filament:plugin lets you create additional plugin classes inside the same module. Use this when a module ships several independently toggleable surfaces (e.g. a BlogPlugin and a BlogCommentsPlugin) and the host app may want only one. Note that auto-registration applies to all discovered plugins when auto-register-plugins: true — if you need selective enabling, flip that to false and register the plugin classes you want by hand in the panel.

Multi-panel modules

module:filament:panel generates a panel and a matching Providers/Filament/<Name>PanelServiceProvider.php. Registration of resources to a panel is then plain Filament — discoverResources() / discoverPages() / discoverWidgets() inside that provider's panel() method. The package wires modules to panels; it does not override Filament's per-panel discovery. So to scope a resource to one panel:

  1. Generate it into the panel's subdirectory (e.g. Filament/ReportsPanel/Resources).
  2. Make only ReportsPanelServiceProvider discover that path.
  3. Keep ModulesPlugin on the main panel so the cross-panel links appear.

Access control — use the wrapper base classes, not the Filament ones

Swap the imports in your generated classes:

use Coolsam\Modules\Resource;            // instead of Filament\Resources\Resource
use Coolsam\Modules\Page;                // instead of Filament\Pages\Page
use Coolsam\Modules\TableWidget;
use Coolsam\Modules\ChartWidget;
use Coolsam\Modules\StatsOverviewWidget;

Or, if you only want the behaviour on a couple of classes, use CanAccessTrait; directly. Because this is WIP, adopt it module by module rather than retrofitting the whole codebase — and if you're already using a permissions package (Shield, spatie/permission), verify the two don't fight over canAccess() before committing.

Reuse across projects

The real selling point is portability. A finished module is a Composer package: Modules/Blog/composer.json with a require block, and consuming projects pull it via a VCS/path repository. Two constraints make or break this:

  • Every module must declare nwidart/laravel-modules and Filament as require entries with compatible ranges, or the host's merge-plugin autoload will silently produce a module that can't boot.
  • Never reference the host app's App\ namespace from a module. Contracts, events, and config keys only.

Cross-module communication

Don't let module A use Modules\B\... directly. Laravel events + a config/blog.php published from the module are the cheap path; a small interfaces package is the expensive-but-correct path once you have more than ~4 modules.


Gotchas and Tips

mode: both produces duplicate navigation

This is the most common first-day bug. In both mode, a module's resources get registered into the host panel and the module gets its own panel with the same resources. You'll see every resource twice. Either commit to plugins mode, or use panels mode with clusters and skip resources at the host level — but don't leave both on "just in case".

Autoload is a manual step

php artisan module:make Foo does not update Composer's autoloader. Run composer dump-autoload after creating a module, or you'll get "class not found" on the generated plugin. The merge-plugin entry in composer.json handles the module's own composer.json requires, but the PSR-4 map still needs refreshing.

Clusters and panels are not the same axis

A cluster groups navigation within a panel; a panel is a separate Filament app entirely. If you set clusters.enabled: true and generate per-panel folders, you can end up with a cluster whose resources belong to a different panel — they simply won't render. Rule of thumb: pick clusters-per-module for a single shared panel, or panels-per-module for isolation. Doing both is only worth it for large domains.

The mode value is not validated loudly

An invalid or misspelled mode string falls back to a default rather than erroring. If navigation looks wrong and nothing else explains it, php artisan config:show filament-modules and compare against the ConfigMode enum's cases.

Caching bites differently than in a normal app

php artisan optimize:clear is the standard fix, but for module-related weirdness also clear nwidart's own cache (php artisan module:clear-compiler if present in your install, or delete bootstrap/cache/modules.php / modules_statuses.json staleness). Filament component caching (php artisan filament:clear-cached-components on 4.x/5.x) is the other classic culprit when a newly generated resource 404s after you've already visited the panel.

The commands are interactive-only

Every generator (module:filament:resource etc.) prompts. There is no documented --module= / --model= non-interactive surface in the README. Practical consequence: you cannot script scaffolding in CI, and you should not try to. Do scaffolding locally and commit.

WIP features — treat the labels as load-bearing

The README explicitly marks widgets, themes, and the entire access-control layer as WIP. Widgets generate but expect to inspect the output before trusting placement. Themes inside modules are the least mature area — if per-module branding matters, use Filament's panel-level theme config instead of module:filament:theme for now.

Version matrix is tight and non-negotiable

v5 requires PHP 8.3+, Laravel 11/12/13, Filament 4/5, laravel-modules 11+. On Filament 3 you must stay on coolsam/modules 4.x; on Laravel 10, 3.x. The package also has a fast Filament 4→5 cadence, so pin an exact minor version in composer.json and read the changelog before bumping — a Filament minor can move enough panel internals to break navigation wiring.

Two Resource classes, one namespace collision

Coolsam\Modules\Resource and Filament\Resources\Resource both exist. PHP will not warn you — you'll just get a resource that silently can't be found by discovery, or one that skips access control. Adopt a consistent import convention (e.g. always alias: use Coolsam\Modules\Resource as ModuleResource;) so a code review catches a wrong base class instantly.

Panel providers need real registration

module:filament:panel writes the provider and the panel class, but the module's own service provider must be loaded (normally automatic via nwidart) for the panel to appear in Laravel's provider graph. If a module panel 404s, check php artisan about / php artisan module:list — the module's service provider not being booted is the usual reason.

Testing inside modules

Module tests live in Modules/<Name>/tests and run through the module's own PHPUnit config. Two pragmatic tips: (1) point the module's testsuite at the same phpunit.xml schema as the host so CI just works; (2) for Filament resource tests, use livewire() assertions against the generated pages rather than asserting on navigation — navigation wiring is the package's job and is exercised upstream.

Bus factor

212 stars, single maintainer, fast-moving target surface (Filament majors). Budget for the possibility of maintaining a fork or pinning hard. The good news: the surface area you actually depend on is small — ModulesPlugin, a config file, and a set of generator commands. If you had to replace it, a custom PanelProvider loop over nwidart modules gets you 80% of the way back in an afternoon.

Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
se7enxweb/flysystem
lampwick/laravel
jeslage/statamic-emojis
micahdshackelford/rmap-laravel
draftsmaninc/draftsman
square-bit/laravel-volition
himito/laravel-mailman
htinlynn/genius-helper
maco-studios/hephaestus
elmogy/larafast
xeviant/laravel-paystack
gorilladash/laravel-website-sdk
martialbe/flysystem-oss
chondal/3312client
jeroen-g/flickr
opscale-co/nova-genbi
itsjustvita/laravel-retention
tequilarapido/trackit
gregsphotography/site-builder-filament
cabaoctavio/laravel-query-log