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

Laravel Modules Laravel Package

aiwozhe/laravel-modules

Organize Laravel apps into self-contained modules with their own controllers, models, views, migrations, routes, and config. Includes Artisan commands to scaffold, publish, enable, disable, and manage modules, making large codebases easier to structure, reuse, and maintain.

View on GitHub
Deep Wiki
Context7

Getting Started

1. Install and publish config

# Laravel 13
composer require nwidart/laravel-modules:^13.0

# Laravel 12 and below — stay on the previous major
composer require nwidart/laravel-modules:^12.0

php artisan vendor:publish --provider="Nwidart\Modules\LaravelModulesServiceProvider"

The repository is the well-known nWidart/laravel-modules codebase (MIT, actively released). v13.0.0 is a framework-compatibility major: it adds Laravel 13.x support and nothing else per the changelog — no new generators, no config schema changes, no renamed APIs. Treat it as a version-pin decision, not a feature upgrade: ^13 for Laravel 13 apps, ^12 (or lower) for anything older. Check composer why-not nwidart/laravel-modules 13.0.0 if the constraint refuses to resolve — it's almost always the framework version, not the package.

If your composer.json uses the aiwozhe/... fork name, confirm the fork's vendor/ provider namespace matches Nwidart\Modules\... before writing any code against it — mixing provider namespaces is the fastest way to a broken install.

2. Wire up autoloading

Modules live under Modules/ and are namespaced Modules\{Name}. Pick one strategy and stick to it:

  • Add "Modules\\": "Modules/" to composer.json → autoload.psr-4 and run composer dump-autoload, or
  • Rely on each module's own composer.json and run php artisan module:dump (which shells out to Composer per module).

The first option is simpler for a monorepo-style app; the second keeps modules independently installable but is easier to get wrong in CI.

3. Create your first module

php artisan module:make Blog
php artisan module:list          # confirms name, status, path

You now have Modules/Blog/ with app/, config/, database/{migrations,seeders,factories}, resources/views, routes/{web,api}.php, module.json, and a BlogServiceProvider.

4. First real use case

php artisan module:make-model Post Blog -m   # model + migration
php artisan module:make-controller PostController Blog
php artisan module:migrate Blog

Then register the route in Modules/Blog/routes/web.php, return a view from Modules/Blog/resources/views/index.blade.php, and render it with view('blog::index'). php artisan module:route-list shows you the registered module routes without grepping the whole app.

Where to look first

  • config/modules.php — namespace, paths, activator, caching, generator paths, stubs.
  • Modules/Blog/module.json — providers, aliases, priority, files, keywords.
  • modules_statuses.json (project root) — source of truth for the file activator.
  • The package's release tags — majors track Laravel majors, so "which version of the package" is answered by "which version of Laravel".

Implementation Patterns

Upgrade to v13 as a paired framework bump. v13.0.0 only widens the framework constraint, so the migration is mechanical but order-sensitive:

# 1. bump Laravel first, resolve, then the package
composer require laravel/framework:^13.0 --with-all-dependencies
composer require nwidart/laravel-modules:^13.0 --with-all-dependencies

# 2. re-establish the module wiring
composer dump-autoload
php artisan module:dump            # only if modules ship their own composer.json
php artisan vendor:publish --provider="Nwidart\Modules\LaravelModulesServiceProvider" --tag=config --force
php artisan config:clear && php artisan cache:clear

# 3. verify before caching
php artisan module:list
php artisan module:route-list

Do the upgrade on a branch with at least one module enabled and one disabled, so you exercise both the boot path and the "silently absent" path.

Pin per-environment expectations in CI. Because the package majors track Laravel majors, a matrix entry like laravel: 13.* → modules: ^13 and laravel: 12.* → modules: ^12 prevents the classic "works locally, fails in CI" resolver mismatch. If you support multiple framework versions across branches, keep the constraint in each branch rather than trying to widen it globally.

Do not expect new generator flags from v13. Every generator, stub path, and paths.generator key described below is unchanged from v12. If a tutorial mentions a module:make-* option you can't find, it's a version-skew issue, not a v13 feature.

Vertical slices, not layers. A module should own its models, migrations, routes, views, policies, and service provider. Cross-module contracts belong in app/Contracts or a Core module; two feature modules importing each other's Eloquent models is the classic path to a dependency knot.

Generator-driven workflow. Almost everything is scaffolded, and the module name is the second positional argument:

php artisan module:make-controller Api/PostController Blog
php artisan module:make-request StorePostRequest Blog
php artisan module:make-event PostPublished Blog
php artisan module:make-listener NotifySubscribers Blog --event=PostPublished
php artisan module:make-test PostTest Blog

module:use Blog sets the "current module" so you can drop the second argument for a session of scaffolding. Use config/modules.php → paths.generator to redirect where each artifact lands, and the stubs settings if you want house-style generated code (typed properties, strict types, etc.).

Service provider as the module's wiring point. Keep registration out of app/Providers and inside the module:

public function register(): void
{
    $this->app->bind(PostRepository::class, EloquentPostRepository::class);
}

public function boot(): void
{
    $this->loadMigrationsFrom(module_path('Blog', 'database/migrations'));
}

Register any additional provider you create in module.json → providers, otherwise it silently never boots. Use priority there when one module's provider must boot before another's.

Routes. The generated RouteServiceProvider loads routes/web.php and routes/api.php. Add extra files (console, admin, webhooks) by editing that provider — don't sprinkle Route:: calls into the main provider.

Migrations and seeders stay inside the module. Use module:migrate Blog, module:seed Blog, module:migrate-refresh Blog. Avoid module:publish-migration unless you have a hard requirement for a single database/migrations folder; copying migrations to the root reintroduces filename collisions across modules.

Factories need explicit resolution. Laravel's convention guesses Database\Factories\...; module factories live in Modules\Blog\Database\Factories. Either override newFactory() on the model or register a Factory::guessFactoryNamesUsing resolver in a service provider.

Scheduling and long-running concerns. In modern Laravel skeletons, register schedules from the module provider rather than a central Kernel:

$this->callAfterResolving(Schedule::class, function (Schedule $schedule) {
    $schedule->command(PublishScheduledPosts::class)->hourly();
});

Runtime inspection. The Module facade is genuinely useful for feature-flag-ish behavior:

use Nwidart\Modules\Facades\Module;

Module::isEnabled('Blog');   // guard routes, nav items, tests
Module::allEnabled();
Module::find('Blog')->getPath();

Testing. Add each module's tests/ directory to phpunit.xml (or maintain per-module phpunit.xml and run with --configuration), and use RefreshDatabase with loadMigrationsFrom already handled by the provider. Gate module-specific tests on Module::isEnabled() only if modules are genuinely optional; otherwise test them unconditionally. On the v13 bump specifically, run the suite twice: once with config:cache/route:cache applied and once without — that's where framework-version differences surface.

Deployment order matters:

composer install --no-dev --optimize-autoloader
php artisan module:dump          # if modules have their own composer.json
php artisan module:migrate --all
php artisan config:cache
php artisan route:cache
php artisan view:cache

Enable/disable modules before caching config and routes, not after.

Gotchas and Tips

  • v13 is a breaking constraint bump, not a feature release. Its only upstream content is Laravel 13.x compatibility (PRs #2138, #2146). If you're not on Laravel 13, there is no reason to take it — and if you are, it's mandatory. Don't expect any behavior change to fix an existing bug; the changelog says nothing was touched.
  • Don't partially upgrade. Installing laravel-modules:^13 alongside laravel/framework:^12 will either fail resolution or leave you with a package built against APIs your framework doesn't have. Bump both in the same composer transaction with --with-all-dependencies.
  • Re-publish config after the major. Even when the changelog claims no config changes, --tag=config --force on a branch costs nothing and surfaces any schema drift as a readable diff instead of a runtime Undefined array key.
  • Clear caches on both sides of the upgrade. config:clear, cache:clear, then re-config:cache. A stale cached module list from v12 + a v13 autoloader is a confusing, hard-to-reproduce failure mode.
  • "Class not found" right after module:make. New module namespaces aren't in the autoloader yet. Run composer dump-autoload (or module:dump). This is the single most common first-hour failure, and it happens again after every framework/package major.
  • View namespaces are the lowercased module alias, not the class namespace. Modules\Blog → view('blog::index'), not view('Blog::index'). Same story for translations: __('blog::messages.welcome').
  • config:cache freezes the module list. Enabled/disabled state and paths get baked in. If you toggle modules in production, re-run config:cache; if modules mysteriously vanish in a cached environment, this is why.
  • Stale module cache. The activator caches the discovered module list (config('modules.caching')). If module:list shows a module that no longer exists, or a fresh module doesn't appear, run php artisan cache:clear (or temporarily set caching.enabled => false while debugging).
  • Disabled modules 404 rather than error. A disabled module's providers never boot, so its routes and views simply don't exist. Check module:list before chasing a routing bug.
  • Changing modules.namespace is a one-way door. It rewrites the folder and every generated namespace; existing modules, Composer autoload entries, and cached configs all need to be regenerated. Decide on Modules up front.
  • Composer merge plugins fight with module:dump. If you use wikimedia/composer-merge-plugin, you can end up with duplicated PSR-4 entries and ambiguous class resolution. Pick one autoload strategy.
  • Migration class name collisions. Two modules each generating 2024_01_01_000000_create_users_table.php is fine inside Modules/*/database/migrations, but publishing both into database/migrations will break. Keep them in place.
  • Custom providers must be listed in module.json. The generator creates the main provider entry for you; anything you add later is your responsibility. A provider that never appears in module.json produces silent, hard-to-trace behavior differences between environments — and is the first thing to re-check if a module stops booting post-upgrade.
  • priority controls provider boot order. If module B's listeners depend on module A's bindings, set A's priority lower. Without it, ordering is effectively arbitrary.
  • module:delete is not a full cleanup. It removes the folder but leaves modules_statuses.json entries, published assets under public/modules/*, and Composer autoload entries. Clean those manually and re-dump.
  • Factory guessing fails silently-ish. Post::factory() throws "factory not found" rather than resolving to the module namespace. Override newFactory() on the model — it's explicit and survives refactors.
  • Route caching and closures. route:cache fails on closure routes, which people write inside module route files. Use invokable controllers in modules if you intend to cache routes.
  • Laravel 11+ has no HTTP Kernel — and that's now two majors behind the current one. Middleware formerly registered in Kernel.php must be registered in a module service provider ($middleware->web(append: [...])), and the package's provider must be in bootstrap/providers.php (auto-discovery normally handles this — verify after upgrading a skeleton, and re-verify on the v13 bump).
  • Module asset publishing is opt-in. module:publish Blog copies the module's assets to public/modules/blog; reference them as asset('modules/blog/...'). Forgetting this step yields 404s for CSS/JS that exist fine on disk.
  • Extension points worth knowing: paths.generator (relocate generated files), stubs.enabled / stubs.path (house-style scaffolding), activator (file vs database, the latter needs its migration published and run), and the Module facade for runtime checks in navigation, middleware, or tests. None of these changed in v13 — if a config key is missing, suspect a stale published config rather than the upgrade.
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.
socialdept/atp-orm
cboxdk/laravel-assistant-client
socialdept/atp-resolver
socialdept/tenant-domains
tautvydasr/blade-class-names
socialdept/color-scales
maherelgamil/controller
coleus/widgets
coleus/table
coleus/users
coleus/music
coleus/health
coleus/calendar
coleus/notes
coleus/support
coleus/settings
coleus/apps
seampact/laravel
emmyconceptng/laravel-ng-postcode
jjsquady/round-robin