savannabits/modular
Savannabits Modular is a Laravel package for modular application development. It helps organize code into self-contained modules with their own routes, migrations, views, and service providers, making large projects easier to structure and maintain.
modular is a module-system package for Laravel (from the Savannabits team, the same author as filament-modules), so the first thing to internalise is that it is a structure + autoloading package, not a framework of its own. It gives you a Modules/ (configurable) directory, a generator, and lifecycle commands so that each module owns its own routes, migrations, views, providers and translations.
Minimal setup:
composer require savannabits/modular
php artisan vendor:publish --tag=modular-config # verify the tag name in the README
Then look at three things before writing any code:
config/modular.php — this is the source of truth for where modules live, what paths get auto-registered (routes, migrations, views, translations), and the namespace prefix used for generated classes. Read it end to end; almost every "why isn't my route registered" question is answered here.Modules/ directory after your first module — comparing a generated skeleton against the config is the fastest way to learn the conventions.php artisan list and grep for the module namespace, since command names are the main API surface and are still shifting in the 0.1.x line.First use case: generate a module, confirm the service provider is auto-discovered, add a route, and hit it.
php artisan module:make Blog # confirm exact signature with `php artisan help`
php artisan module:list
composer dump-autoload
If the module's route responds, your wiring is correct and everything after that is conventional Laravel work inside a namespaced folder.
One caveat up front: 0.1.1 is docs and Pint formatting only — no functional change from 0.1.0. But the version number itself is the signal: this is pre-1.0 software. Treat the public API (command names, config keys, generated stub namespaces) as unstable and pin the version in composer.json rather than tracking ^0.1.
Vertical slices per module. The productive pattern is to put a whole feature — model, migration, policy, controller, route file, Blade/Livewire or Filament resource, tests — inside one module, and keep app/ for genuinely shared concerns (base User model, service providers, cross-cutting middleware). Resist putting business logic in app/ and only controllers in modules; you lose the main benefit.
Autoloading is the integration point. The package registers each module's providers, route files, migrations, views and translations based on config. That means most integration "just works" and the failure mode when it doesn't is almost always a stale autoloader or a namespace mismatch. After every module:make, run composer dump-autoload — bake it into your muscle memory or a Composer script.
Cross-module communication: contracts and events, not direct model references. Two modules referencing each other's Eloquent models creates a dependency graph that defeats the purpose. Instead:
Migrations. Each module keeps its own migrations directory. Prefer running module:migrate per module during development so failures are attributable, and be deliberate about ordering — cross-module foreign keys are the classic modular-architecture trap. Either keep FKs within a module, or use a shared "core" module that migrates first. In CI, migrate everything in dependency order and assert it works from a clean database.
Per-module testing. Give each module its own tests/ directory with its own phpunit.xml (or a testsuite entry in the root config) so a module can be tested in isolation and, ideally, extracted later. This is the single highest-value habit in a modular codebase and the one most teams skip.
Filament / admin panels. If you're building admin UI, pair this with savannabits/filament-modules — it's from the same author and designed to discover Filament resources and pages inside modules. Keep panel registration in the module's provider so a module can be enabled/disabled without editing central config.
Enable/disable as a deployment lever. The lifecycle commands are useful beyond local dev: gating a module behind an enable flag lets you ship code dark and turn features on per environment. If you use this, make sure your migrations and config are safe to run while a module is disabled — a disabled module whose migrations ran is a subtle and confusing state.
Code style. 0.1.1 applied Pint to the package's own source. Mirror that in your app: run Pint across Modules/ too (it's easy to exclude that path by accident when your pint.json scopes to app/). Add Modules to your Pint paths explicitly.
composer dump-autoload; (2) namespace in the generated class matches config/modular.php; (3) provider listed/discovered correctly; (4) php artisan config:clear — a cached config with a different modules path is a genuinely nasty one; (5) module:list to confirm the package sees the module at all.config:cache baked in and then mounting modules elsewhere will silently break discovery.App\. Generated modules use their own root namespace (e.g. Modules\Blog\...). Don't half-migrate a class into a module — a model split across App\Models and Modules\Blog\Models with the same table name is a debugging afternoon you don't want.route:cache requires all module route files to be resolvable and closure-free. Test php artisan route:cache && php artisan route:clear in CI; modular route registration is a common place for this to break only in production."savannabits/modular": "0.1.1") and read the diff of the package source on upgrade, not just the release notes — 0.1.1's notes ("README, Pint") say nothing about stub or config changes, so verify rather than assume.php artisan about is a good first stop in recent Laravel versions) rather than guessing from the config file. The gap between "what config says" and "what got registered" is where the bug lives.tests/ folder, an Actions/ directory, whatever your house style is). Doing this at the start of a project is far cheaper than retrofitting fifty modules later.app/ is a net cost. Adopt this when you have a genuine boundary (a domain you might extract, a team boundary, an optional/pluggable feature), not because the word "modular" sounds like architecture.How can I help you explore Laravel packages today?