edalzell / laravel-features
Self-contained feature modules for Laravel with auto-registration of routes, migrations, views, events, and seeders
Requires
- php: ^8.3
- composer/composer: ^2.8
- edalzell/laravel-features-plugin: ^0.2
- illuminate/contracts: ^12.0 || ^13.0
Requires (Dev)
- brain/monkey: ^2.7
- larastan/larastan: ^3.0
- laravel/pint: ^1.14
- nunomaduro/collision: ^8.8
- orchestra/testbench: ^10.0 || ^11.0
- pestphp/pest: ^4.0
- pestphp/pest-plugin-arch: ^4.0
- pestphp/pest-plugin-laravel: ^4.0
- phpstan/extension-installer: ^1.4
- phpstan/phpstan-deprecation-rules: ^2.0
- phpstan/phpstan-phpunit: ^2.0
- spatie/laravel-ray: ^1.40
This package is auto-updated.
Last update: 2026-08-05 04:44:28 UTC
README
Add self-contained features to your Laravel app or package, including all resources/routes/etc.
.
└── app/
...
└── features/
│ └── MyGreatFeature/
│ ├── config/
│ │ └── my-great-feature.php
│ ├── database/
│ │ ├── factories
│ │ ├── migrations
│ │ └── seeders
│ ├── resources
│ ├── routes
│ └── src/
│ ├── Models
│ ├── ...
│ └── ServiceProvider.php
Each feature behaves like a mini Laravel app. The following are auto-registered and booted:
| Phase | What |
|---|---|
| Register | Config, Migrations, Routes, Seeders, Views |
| Boot | Config publishing, Listeners, Policies, Seeders |
Installation
You can install the package via composer:
composer require edalzell/laravel-features
Usage
To add a new feature in your app:
php artisan make:feature MyGreatFeature
To add feature to a package:
php artisan make:feature MyGreatFeature the-dev/my-package
This creates a ServiceProvider that extends FeatureServiceProvider — everything is auto-registered with no further code required.
Option 1: Extend FeatureServiceProvider
The zero-friction path. Your provider gets boot() and register() for free:
class MyGreatFeatureServiceProvider extends FeatureServiceProvider { // nothing needed — everything is auto-registered }
Override any of these protected methods to customise behaviour:
protected function configFileName(): string // default: kebab-cased feature name protected function configGroup(): string // default: '' (no subdirectory) protected function configPublishHandle(): string // default: kebab-cased feature name protected function featuresPath(): string // default: base_path('features/FeatureName')
Option 2: Standalone Features object
When your provider already extends another class, wire up Features directly:
use Edalzell\Features\Features; class MyServiceProvider extends SomeOtherProvider { private Features $features; public function __construct(Application $app) { parent::__construct($app); $this->features = (new Features($this)) ->path($this->featuresPath()) ->name($this->name()) ->configFileName($this->configFileName()) ->configGroup($this->configGroup()) ->configPublishHandle($this->configPublishHandle()); } public function boot(): void { $this->features->bootFeature(); } public function register(): void { $this->features->registerFeature(); } }
Features derives the path, namespace, and app from your provider via reflection. You only need to call the fluent setters when overriding the defaults.
Auto-discovering features
Use the HasFeatures trait in any service provider to automatically register all features from a directory. In your app, add it to AppServiceProvider:
use Edalzell\Features\Concerns\HasFeatures; class AppServiceProvider extends ServiceProvider { use HasFeatures; public function register(): void { $this->registerFeatures(app_path('../features'), 'App\\Features'); } }
For a package, add it to your package's main service provider:
use Edalzell\Features\Concerns\HasFeatures; class MyPackageServiceProvider extends ServiceProvider { use HasFeatures; public function register(): void { $this->registerFeatures(); } }
In a package, registerFeatures() defaults to looking in <package-root>/features/ and registering providers under YourPackage\Features\FeatureName\ServiceProvider. Pass explicit arguments to override either default:
$this->registerFeatures('/path/to/features', 'My\\Namespace\\Features');
Features outside the app
A feature works from anywhere — the app, a package, or a directory outside the app entirely, such as a monorepo where two apps share one set of features:
gym/
├── apps/
│ ├── server/
│ └── mobile/
└── shared/
└── features/
└── Scheduling/
Two things need wiring, in each app that uses them.
Autoloading — declare the directory and its namespace in composer.json, and the plugin generates PSR-4 entries for every feature it finds:
"extra": { "laravel-features": { "paths": { "../../shared/features": "Shared\\Features" } } }
Registration — point registerFeatures() at the same directory:
$this->registerFeatures(base_path('../../shared/features'), 'Shared\\Features');
The app's own features/ directory is still scanned, so app-local and shared features can coexist.
Testing
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
License
The MIT License (MIT). Please see License File for more information.