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

bchalier/laravel-modules

Laravel Modules lets you split a Laravel application into self-contained modules, each with its own routes, views, migrations, config, and service providers. It provides the tooling and bootstrapping needed to register, organize, and manage modular codebases more cleanly.

View on GitHub
Deep Wiki
Context7

Getting Started

Install and confirm compatibility first. The package is small (9 stars, 0 dependents) and its last release was October 2021, so before anything else check the composer.json constraints against your Laravel/PHP version:

composer require bchalier/laravel-modules
composer show bchalier/laravel-modules   # inspect require/require-dev

If your app is on Laravel 10/11, expect to either constrain yourself or fork the package — plan for that decision up front rather than halfway through a refactor.

Read these two things before writing code:

  1. The package's own service provider in vendor/bchalier/laravel-modules/src/ — this tells you exactly how discovery works (which directory it scans, how it derives namespaces, how it registers providers). With sparse docs, the source is the documentation.
  2. The published config (php artisan vendor:publish --provider=...) — typically it exposes the modules path and namespace root. Confirm the actual keys rather than assuming.

Minimal first module. The convention is a directory plus a service provider; something along these lines (adapt names to what the package's discovery expects):

modules/
  Billing/
    Providers/BillingServiceProvider.php
    routes/web.php
    config/billing.php
    Database/Migrations/
    src/...

The service provider is the seam: it registers routes, merges config, and loads migrations. Get one module booting (php artisan route:list | grep billing, php artisan migrate --pretend) before extracting real code.

First real use case. Pick the most bounded domain in your app — billing, notifications, an integrations surface — not your core domain. A module that only depends inward on App\ contracts is easy to extract and easy to delete if the experiment fails. That is the single highest-value first step with this package.

Implementation Patterns

Provider-as-boundary. Treat each module's service provider as its only public entry point. Everything else lives under the module namespace and is treated as internal. Register bindings, event listeners, and route files there; never reach into another module's internals from App\.

Config namespacing. Merge config under a module-specific key (billing.*) so config() lookups never collide across modules:

$this->mergeConfigFrom(__DIR__.'/../config/billing.php', 'billing');
$this->publishes([...], 'billing-config');

Same discipline for views (loadViewsFrom(..., 'billing')) and translations — the string namespace is what prevents the "two modules both have index.blade.php" problem.

Migrations. loadMigrationsFrom() per module works, but ordering is global. Adopt a convention: prefix migration filenames with a module-unique timestamp range, or keep cross-module foreign keys out of module migrations entirely and let the app-level migrations own the joins. In practice, modules that own their tables outright and reference other modules only by ID (no FK constraints) migrate far more cleanly.

Cross-module communication. Three patterns that hold up:

  • Contracts in App\Contracts — modules depend on interfaces, the app binds implementations.
  • Events — the loosest coupling; good for side effects, bad for anything needing a return value.
  • A thin application service — for synchronous reads across modules.

Avoid a fourth: modules calling each other's concrete classes. That's how you get an acyclic-graph problem six months in.

Directory layout that scales. Split each module into src/ (PSR-4) plus a sibling routes/, config/, database/, resources/. Keeping non-PHP assets outside src/ means your autoload mapping stays a single clean entry per module and tooling (PHPStan, Pint, IDE indexing) doesn't choke on blade files.

Testing. Add a phpunit.xml testsuite per module or a tests/ directory with its own namespace, and rely on the module provider being booted in your base TestCase. Module-level feature tests that boot only their provider are the fastest way to prove a module is genuinely decoupled.

CI hygiene. Discovery means new modules appear without registration — so add a CI step that fails if php artisan route:list or config:cache errors. Modules that register broken routes are otherwise invisible until runtime.

Gotchas and Tips

Assume you may need to fork. With zero dependents and a three-year-old release, treat the package as vendored code you own. If it's small (likely, given the scope), copying src/ into app/ or a local path repository is often cheaper than fighting version constraints. Budget an afternoon to read every file — it's probably a few hundred lines.

Caches will bite you. Module discovery is almost certainly cached. After adding/renaming a module:

php artisan cache:clear
php artisan config:clear
php artisan route:clear
composer dump-autoload

If a new module "doesn't exist," it's autoload or cache 90% of the time, not the package.

Autoload is the most common failure. Discovery finds the directory but the class won't resolve unless the namespace is mapped. Check composer dump-autoload -o output and confirm the module namespace is actually reachable. If the package relies on runtime spl_autoload_register, that breaks under --optimize-autoloader and preloading — verify, and prefer explicit composer.json PSR-4 entries.

Route collisions. Two modules both defining Route::get('/settings') will silently resolve to whichever registers last. Always prefix:

Route::prefix('billing')->name('billing.')->group(...);

Then verify with php artisan route:list --path=billing.

Provider boot order. Discovery order is filesystem-dependent unless the package sorts explicitly. Anything depending on another module's bindings (middleware aliases, view composers) must be deferred to boot() or resolved lazily — never done in register().

Config caching interaction. mergeConfigFrom is fine under config:cache, but env() calls inside module config files return null once cached. Standard Laravel rule, but it bites harder here because module configs are often written by people who forget the module is still just a config file.

Debugging checklist. When something doesn't load: php artisan about (provider list) → php artisan route:list → confirm the service provider appears in bootstrap/providers.php / config/app.php → re-check discovery cache. If the provider is absent entirely, discovery isn't seeing the directory (path config or permissions).

Extension points worth knowing. Before forking, look for a configurable scan path/namespace in the published config, and for any interface the package uses for module resolution. Overriding the discovery path is usually a supported escape hatch; modifying discovery logic is a fork.

Migration ordering across modules. If loadMigrationsFrom yields nondeterministic ordering, you'll get intermittent FK failures in CI but not locally (different filesystem ordering). Make migrations order-independent rather than relying on discovery order.

Don't put the app's core in a module. Keep App\Models\User, auth, and anything the framework's own scaffolding assumes at the app level. Modules that own the user table create circular dependencies with Laravel's auth guards and password reset flows that are annoying to untangle later.

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.
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons
mmccook/php-json-canonicalization-scheme
middlewares/firewall
qubiqx/montapacking
nyoncode/laravel-package-console
pnscripts/filament-broadquill-lite
pnscripts/laravel-sentfold-lite
vleap/mx-sdk-laravel-internal
twidpay/utils
htetmyathlaing/scout-elasticsearch-driver
artisan-toolbox/front-awesome
ls/client-assistant
users-au/laravel-client
cgv/localized-resource