edalzell/laravel-features

Self-contained feature modules for Laravel with auto-registration of routes, migrations, views, events, and seeders

Maintainers

Package info

github.com/edalzell/laravel-features

pkg:composer/edalzell/laravel-features

Transparency log

Statistics

Installs: 271

Dependents: 0

Suggesters: 0

Stars: 20

Open Issues: 0

v0.6.3 2026-08-05 04:44 UTC

README

Latest Version on Packagist GitHub Tests Action Status GitHub Code Style Action Status Total Downloads

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.