caffeinated/modules
Caffeinated Modules is a Laravel package for organizing applications into self-contained modules. Each module can bundle its own routes, controllers, views, migrations, translations, and assets, with artisan commands and service providers for smooth registration and management.
Install the package with Composer:
composer require caffeinated/modules
If your Laravel version does not auto-discover the provider, register it in config/app.php:
Caffeinated\Modules\Providers\ModulesServiceProvider::class,
and add the facade alias if you plan to use it:
'Module' => Caffeinated\Modules\Facades\Module::class,
Publish the package config:
php artisan vendor:publish --provider="Caffeinated\Modules\Providers\ModulesServiceProvider"
Confirm the exact provider class and publish tag inside vendor/caffeinated/modules if your version differs. The first place to look after publishing is config/modules.php: it controls the modules path (normally Modules/), namespace, and discovery behavior. Then inspect the generated Modules/ directory.
The fastest first use case is a throwaway module:
php artisan module:make Blog
php artisan module:list
This scaffolds a module with a module.json manifest, a service provider, routes, controllers, views, migrations, and seeders. Add a route in Modules/Blog/Routes/web.php, a controller in Modules/Blog/Http/Controllers, return view('blog::index') from a generated view, run php artisan module:migrate blog, and hit the route. That single loop—scaffold, route, migrate, view—is the core day-to-day workflow. Run composer dump-autoload if newly generated classes are not resolving.
Treat modules as business-capability slices, not as a replacement for Laravel’s normal app/ structure. Good candidates are Blog, Billing, Shop, Admin, or Reporting; poor candidates are “Models”, “Controllers”, or “Helpers”. Keep the host app thin and let each module own its HTTP layer, routes, views, migrations, and domain services.
A typical module layout looks like this:
Modules/Blog/
module.json
Providers/BlogServiceProvider.php
Http/Controllers/
Http/Requests/
Models/
Routes/web.php
Routes/api.php
Resources/views/
Resources/lang/
Database/Migrations/
Database/Seeds/
Config/config.php
Use the generated module service provider as the module’s composition root. Register its routes, views, translations, config merging, and container bindings there. Keep the host application’s providers focused on cross-cutting concerns.
Day-to-day conventions:
Modules\Blog\...; views are referenced as blog::index; translations as blog::messages.welcome.web or api middleware explicitly inside the module route files. If you add an API module, define its prefix and middleware in the module’s route group rather than assuming the app-level RouteServiceProvider handles it.php artisan module:migrate blog and php artisan module:seed blog. In deployment scripts, loop over enabled modules or run the module commands explicitly rather than assuming a global migrate command covers everything.module:enable blog and module:disable blog are backed by the module manifest. This is useful for turning whole feature slices on or off per environment. Guard host code with Module::isEnabled('blog') when a module is optional.Core or Shared module rather than creating hidden coupling.Module::all(), Module::enabled(), or Module::isEnabled('blog') to build navigation, register permissions, or conditionally boot optional integrations.module:publish blog (or the equivalent publish command in your version). Re-publish after changes.For testing, test module internals as unit tests and test integration through the host application as feature tests. In test setups, register module migration paths or run module migrations during bootstrap, and clear module-related caches between tests so discovery state does not leak.
illuminate/* constraints in the package’s composer.json before adopting it. Treat Laravel 8 as the likely practical ceiling; Laravel 9+ may require a fork or a different package.nwidart/laravel-modules if you need a long-lived modular architecture.php artisan config:clear, php artisan route:clear, php artisan view:clear, and composer dump-autoload as appropriate. Do not debug discovery while config or route caches are warm.module.json is the source of truth. Manual edits and merge conflicts can silently change enabled state across environments. Prefer the Artisan commands and review manifest diffs in code review.Blog, namespace Modules\Blog, slug blog, and view namespace blog:: all need to line up. Case-sensitive Linux filesystems expose sloppy casing immediately.blog_posts, to avoid collisions. Rollback order matters when modules reference each other’s tables.Module::isEnabled() and avoid hard dependencies on disabled slices.config('blog.some_key') returns null, the module config was not merged in the service provider, or the config cache is stale. Use mergeConfigFrom in the module provider.php artisan module:list to inspect state, and php artisan tinker with Module::all() or Module::isEnabled('blog') to confirm discovery. Check bootstrap/cache and provider registration order when behavior differs between local and production.How can I help you explore Laravel packages today?