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 Module Laravel Package

akaunting/laravel-module

A Laravel package for modular application development. Create self-contained modules with their own routes, views, migrations, translations, and service providers, then enable, disable, and manage them easily within your app.

View on GitHub
Deep Wiki
Context7

Getting Started

  1. Install and publish the config. composer require akaunting/laravel-module, then publish the config so you can see where modules live and how they are named:

    php artisan vendor:publish --tag=module
    

    You get config/module.php. Inspect paths.modules (default base_path('modules')), namespace (default Modules), and the status/activator settings. The namespace in this file must match the PSR-4 prefix used inside every module.json, or nothing will autoload.

  2. Look at the source before the docs. Documentation is thin and the package is maintained primarily as the module engine for Akaunting. Read, in order:

    • src/Module.php — the value object and its enabled(), disabled(), isEnabled() helpers.
    • The Module facade and its concrete binding — this is your runtime API (Module::all(), Module::enabled(), Module::find($alias), etc.).
    • src/Providers/ — how module service providers, routes, and translations are bootstrapped.
    • The stubs used by module:make — that's the canonical "correct" scaffold.
  3. Create your first module and enable it.

    php artisan module:make Blog
    php artisan module:list          # confirm it exists and is disabled
    php artisan module:enable Blog
    

    The scaffold gives you modules/Blog/module.json, Providers/BlogServiceProvider.php, Routes/, Resources/views/, Resources/lang/, Database/Migrations/. Add a simple route in Routes/web.php, hit it, and you have proven the full loop: file discovered → provider registered → route loaded → response returned.

  4. First real use case. Treat a module as a self-contained feature slice: its own controllers, models, migrations, views, translations, and route files, toggled on or off without touching the host app. The moment you can module:disable Blog and confirm the routes 404 while the rest of the app still boots, you understand the contract.

Implementation Patterns

Everything is wired in the module provider. The generated BlogServiceProvider is the integration point. Typical boot() sequence:

$this->mergeConfigFrom(__DIR__.'/../Config/blog.php', 'blog');
$this->loadRoutesFrom(__DIR__.'/../Routes/web.php');          // and api.php
$this->loadViewsFrom(__DIR__.'/../Resources/views', 'blog');  // namespace: blog::
$this->loadTranslationsFrom(__DIR__.'/../Resources/lang', 'blog');
$this->loadMigrationsFrom(__DIR__.'/../Database/Migrations');

Keep module.json's providers array pointed at this class; the package registers providers for enabled modules during boot. If a route/view "mysteriously" disappears, check that array first.

Route isolation. Put web and API routes in separate files so you can attach different middleware stacks (web vs api) and prefixes. A common convention is to prefix web routes with the module alias (/{alias}/...), but the package does not enforce it — decide once and standardize, because controllers, views, and asset URLs all reference the same alias.

Dependencies and ordering. Declare inter-module dependencies in module.json under requires, and use priority to control provider boot order when a module needs another module's bindings registered first. module:enable on a module with missing dependencies should fail; treat that as a feature, not a bug, and enforce it in your own installers too.

Contracts across modules. Modules should talk through interfaces, not concrete classes. Define a Contracts/ directory in the consuming module and bind implementations in the providing module's provider. This keeps modules independently disable-able, which is the entire point of the package.

Enable/disable as an event surface. Listen for the package's enable/disable events to run side effects: clear application caches, register navigation menu entries, seed permissions/roles, touch a tenant record. These are the natural seams for anything that must survive a toggle without re-running migrations.

Migrate / seed workflow. Module migrations are registered via loadMigrationsFrom, so plain php artisan migrate picks them up once the module is enabled. Order matters: enable the module first, then migrate. For seeding, run module seeders explicitly or wire them into your deploy script — the package does not automatically seed on enable. In tests, RefreshDatabase works only if the module provider has already run loadMigrationsFrom before the first test bootstraps; if migrations are missing, register the module provider explicitly in your TestCase.

Overrides via publish. Treat modules like vendor packages for customization: publish module views/config/lang into the app (Laravel's loadViewsFrom convention means overrides belong in Resources/views/vendor/{alias}/) rather than editing files inside modules/. If your team maintains a fork, this discipline is the difference between a clean upgrade and a merge fight.

Per-tenant or per-company enablement. The package's enabled/disabled status is process-wide (file/config backed). If you need modules enabled per tenant, do not fight the package: keep a database table of tenant→module and gate route registration/menu exposure yourself, or wrap calls behind a service that consults both the package and your tenancy store.

Testing. Feature tests can rely on the enabled set, but module tests are most robust when the module's provider is registered explicitly in the test case. Use Module::find('Blog') to assert enablement in tests that exercise toggle behavior.

Gotchas and Tips

  • Namespace/directory mismatch is the #1 failure. module.json's name/namespace, the directory name, and the class names in providers must agree with config/module.php's namespace. A copy-pasted module that was renamed in only one place throws "class not found" with almost no diagnostic. Run composer dump-autoload and php artisan optimize:clear after structural edits.

  • Composer autoloading does not cover modules. The package has its own loader driven by module.json; if you add new directories or namespaces inside a module and classes stop resolving, clear caches and re-discover. If you switch the modules path or namespace in config, you may also need to update composer.json autoload for IDE/static analysis support even if runtime works.

  • Caches lie after toggling. Route cache, config cache, and the package's own discovery/status caching can all hold stale module info. After module:enable/module:disable in a deploy script, always php artisan optimize:clear (or the targeted route:clear/config:clear) before re-caching. If routes vanish in production but work locally, this is almost always the cause.

  • Disabling a module does not remove its data or migrations. Tables remain, migrations rows remain, and re-enabling picks up where it left off. That's usually right, but it means deleting a module folder leaves orphaned migration records that will break migrate:rollback with "class not found." Remove migration rows deliberately when you retire a module.

  • Renaming the alias is a breaking change. URLs, translation namespaces (blog::), view namespaces, and any stored permission/menu references are keyed to it. Pick an alias at creation and treat it as API.

  • module:list is not package:discover. If a module appears in module:list but its provider never runs, the issue is module.json's providers array, the module's enabled status, or provider priority ordering — not Laravel's package manifest.

  • Command surface varies across versions. Confirm the available commands with php artisan list module before scripting. The package is small and low-velocity (last release Dec 2024, ~125 stars), so pin the version in composer.json, read the changelog before upgrades, and do not assume commands documented in a blog post exist in your version.

  • Version compatibility. Because the package tracks the Akaunting application, verify its Laravel constraint matches your app before upgrading either. A composer resolution failure at upgrade time is far cheaper than a runtime break from an unreleased Laravel minor.

  • Filesystem case sensitivity. On macOS/Windows a module directory named blog with namespace Blog will "work" locally and fail on Linux. Match case exactly.

  • Extension points. The genuinely useful hooks are: the Module facade (query state and paths at runtime), the enable/disable events (side effects), the provider boot() (route/view/translation/migration wiring), and module.json (requires, priority, providers). There is no meaningful plugin system beyond these — do not look for one.

  • Debugging recipe. When a module fails: php artisan module:list → verify module.json namespace/providers → composer dump-autoload → php artisan optimize:clear → confirm the provider's boot() actually calls loadRoutesFrom/loadViewsFrom → check the route prefix and middleware in the route file. That order resolves nearly every issue.

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.
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
noith/settings
kda/laravel-package
kda/filament-dynamic-navigation
bypersoft/signbox
pairus/product-data
fayne/laravel-filament-cms-core