0

Package service providers — building installable Laravel packages

Advanced5 min read·lv-03-004
laravel-src

Concept

Building installable Laravel packages requires a service provider that handles publishing, registering, and loading the package's resources in a host application. The package's service provider is the API surface — it's how the package integrates with Laravel.

Key service provider package methods:

  • $this->loadRoutesFrom(string $path): Register routes from a file.
  • $this->loadMigrationsFrom(string $path): Register migration path (auto-discovered by artisan migrate).
  • $this->loadViewsFrom(string $path, string $namespace): Register a view namespace ($namespace::view.name).
  • $this->loadTranslationsFrom(string $path, string $namespace): Register a translation namespace.
  • $this->mergeConfigFrom(string $path, string $key): Merge package config with app config. User's app config takes precedence.
  • $this->publishes(array $paths, string|array $groups): Register publishable files/dirs. Users run php artisan vendor:publish --tag=group-name.

Package auto-discovery: Add to composer.json:

json
"extra": {
    "laravel": {
        "providers": ["Vendor\\Package\\PackageServiceProvider"],
        "aliases": {"Package": "Vendor\\Package\\Facades\\Package"}
    }
}

Laravel 5.5+ reads this and auto-registers the provider without requiring the user to edit config/app.php.

Stub-based config publishing: Always provide a publishable config with sensible defaults. Use mergeConfigFrom so the package works without publishing. Publishing lets users customize.

Code Example

php
<?php
namespace Vendor\Analytics;

use Illuminate\Support\ServiceProvider;

class AnalyticsServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        // Merge package defaults with user's published config
        // User's config takes precedence for matching keys
        $this->mergeConfigFrom(
            __DIR__ . '/../config/analytics.php',
            'analytics'
        );

        $this->app->singleton(\Vendor\Analytics\Analytics::class, function($app) {
            return new Analytics(config('analytics'));
        });
    }

    public function boot(): void
    {
        // Load package routes
        $this->loadRoutesFrom(__DIR__ . '/../routes/web.php');

        // Load package views under 'analytics' namespace
        // Usage in templates: @include('analytics::dashboard')
        $this->loadViewsFrom(__DIR__ . '/../resources/views', 'analytics');

        // Load package migrations
        $this->loadMigrationsFrom(__DIR__ . '/../database/migrations');

        // Register publishable assets
        $this->publishes([
            __DIR__ . '/../config/analytics.php' => config_path('analytics.php'),
        ], 'analytics-config');

        $this->publishes([
            __DIR__ . '/../resources/views' => resource_path('views/vendor/analytics'),
        ], 'analytics-views');

        $this->publishes([
            __DIR__ . '/../database/migrations' => database_path('migrations'),
        ], 'analytics-migrations');

        // Combine all publishable under one tag
        $this->publishes([
            __DIR__ . '/../config/analytics.php' => config_path('analytics.php'),
            __DIR__ . '/../database/migrations' => database_path('migrations'),
        ], 'analytics');
    }

    public function provides(): array
    {
        return [Analytics::class];
    }
}
bash
# Users publish the config
php artisan vendor:publish --tag=analytics-config

# Or publish everything under 'analytics' tag
php artisan vendor:publish --provider="Vendor\Analytics\AnalyticsServiceProvider"