zenphp/modulr
ZenPHP Modulr is a Laravel package for building modular applications. Organize features into self-contained modules with streamlined registration and discovery, helping keep large codebases structured, reusable, and easier to maintain.
Modulr is a small, low-adoption Laravel package (1 star, no dependents) that adds modular application structure — splitting a Laravel app into self-contained modules with their own routes, migrations, views, config, and service providers. Because adoption is minimal, treat the README and the package source as the source of truth over any blog posts.
Minimal setup:
Install
composer require zenphp/modulr
Confirm the package supports your Laravel version — check its composer.json illuminate/* constraints before committing. With no dependents and a single maintainer, version compatibility is the first thing to verify.
Publish config and assets (typical for this class of package):
php artisan vendor:publish --provider="ZenPHP\Modulr\ModulrServiceProvider"
Inspect the published config/modulr.php — it usually defines the module path (e.g. app/Modules or Modules/), namespace, and whether modules are auto-discovered.
Where to look first
config/modulr.php — module root path, namespace convention, discovery toggle.stubs/ directory — shows the canonical module skeleton, which tells you far more about intended structure than prose docs.First use case — create one module and move a single existing feature (a small CRUD: controller, routes, migration, views) into it. This verifies discovery, routing, and migration loading before you attempt a larger refactor.
php artisan modulr:make Blog # name may differ; check `php artisan list | grep modulr`
Module-per-feature, not module-per-layer. The value of Modulr appears when each module owns its full vertical slice: Blog/Http/Controllers, Blog/Routes/web.php, Blog/Database/Migrations, Blog/Resources/Views, Blog/Providers/BlogServiceProvider. Keep cross-module coupling explicit — modules should talk through events, contracts, or shared app/Support code, never by reaching into another module's internals.
Service provider as the module's contract. Each module should register its own routes, migrations, views, config, and translations in its provider. Follow Laravel's standard loadRoutesFrom, loadMigrationsFrom, loadViewsFrom, mergeConfigFrom calls — most modular packages rely on these under the hood, so if discovery ever misbehaves you can register manually.
Discovery vs. explicit registration. Prefer explicit registration in config/modulr.php (or a provider) over filesystem auto-discovery for production apps. Auto-discovery is convenient in development but produces surprising behavior when a directory is renamed, and it interacts poorly with config caching if the package doesn't hook into config:cache carefully.
Integration with existing Laravel conventions:
posts.index will clash.2024_01_01_000000_create_blog_posts_table.php.loadViewsFrom namespacing (blog::posts.index) is easy to get wrong.Workflow suggestion: migrate incrementally. Move one leaf feature per PR, run the full test suite, then delete the original files. This keeps git history clean and makes rollback trivial — important given the package's immaturity.
php artisan config:cache / route:cache because cached config snapshots the module list. After adding a module, always: php artisan optimize:clear then re-cache. If routes vanish in production, this is almost always why.composer.json autoload.psr-4 must map the module namespace (e.g. "Modules\\": "Modules/" or "App\\Modules\\": "app/Modules/") exactly as the package expects. Mismatches produce "class not found" errors that look like discovery failures. Run composer dump-autoload after adding modules.Models/User.php or the same route name will silently conflict. Adopt naming conventions early — module-prefixed route names and unique migration filenames.--module flag on migrate. Verify that migrate:fresh and migrate:rollback pick up module migrations; test in a scratch database before trusting it in CI.Modulr facade, avoid it in controllers — inject or resolve via the container so tests can fake it. Config keys read via config('modulr.…') should be cached at boot, not per-request.php artisan optimize:clearcomposer dump-autoloadphp artisan route:list | grep <module> to confirm routes mounted.composer.json and watch for the next release (2026-05-06) since breaking changes are plausible pre-1.0.How can I help you explore Laravel packages today?