Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save garyblankenship/bbd2f0b638bea663a9eaa63e6eec1890 to your computer and use it in GitHub Desktop.

Select an option

Save garyblankenship/bbd2f0b638bea663a9eaa63e6eec1890 to your computer and use it in GitHub Desktop.
Laravel Package Creation: The Complete TL;DR Guide #laravel #php #composer #packages

Laravel Package Creation: The Complete TL;DR Guide

Laravel Packages Are Just Structured PHP with Magic Wiring

Think of a Laravel package as a mini-application that plugs into any Laravel project. It's PHP code organized with specific conventions that Laravel recognizes and automatically integrates. The "magic" happens through Service Providers - special classes that tell Laravel "here's my code, here's how to use it."

Every package needs: a composer.json file (the package's ID card), a Service Provider (the integration bridge), and your actual code. Laravel handles the rest through auto-discovery.

Composer.json Is Your Package's Birth Certificate

{
    "name": "vendor/package-name",
    "type": "library",
    "require": {
        "illuminate/support": "^8.0|^9.0|^10.0"
    },
    "autoload": {
        "psr-4": {
            "VendorName\\PackageName\\": "src/"
        }
    },
    "extra": {
        "laravel": {
            "providers": [
                "VendorName\\PackageName\\PackageServiceProvider"
            ]
        }
    }
}

The name field determines how people install your package (composer require vendor/package-name). The autoload section maps namespaces to directories. The extra.laravel.providers enables auto-discovery - Laravel automatically finds and loads your Service Provider.

Service Providers Are the Universal Plugin System

class PackageServiceProvider extends ServiceProvider
{
    public function register()
    {
        // Bind services into container
        $this->app->singleton(MyService::class);
    }

    public function boot()
    {
        // Publish assets, register routes, load views
        $this->loadRoutesFrom(__DIR__.'/../routes/web.php');
        $this->loadViewsFrom(__DIR__.'/../resources/views', 'mypackage');
        $this->publishes([
            __DIR__.'/../config/mypackage.php' => config_path('mypackage.php'),
        ]);
    }
}

register() runs first and binds services. boot() runs after all packages are registered and handles publishing assets, loading routes, views, and migrations. This two-phase loading prevents dependency conflicts.

Package Structure Follows Convention Over Configuration

src/
├── PackageServiceProvider.php  # The bridge to Laravel
├── Facades/                   # Optional static access
├── Commands/                  # Artisan commands
├── Http/
│   ├── Controllers/          # Package controllers
│   └── Middleware/           # Package middleware
└── Services/                 # Core business logic

config/
└── mypackage.php             # Default configuration

resources/
├── views/                    # Blade templates
└── lang/                     # Translations

database/
├── migrations/               # Database changes
└── factories/                # Model factories

routes/
├── web.php                   # Web routes
└── api.php                   # API routes

This structure mirrors Laravel applications. Laravel automatically discovers and loads files from these locations when you use the appropriate Service Provider methods.

Testing Packages Requires Laravel's Test Environment

// tests/TestCase.php
abstract class TestCase extends Orchestra\TestCase
{
    protected function getPackageProviders($app)
    {
        return [PackageServiceProvider::class];
    }

    protected function defineEnvironment($app)
    {
        $app['config']->set('database.default', 'testing');
        $app['config']->set('database.connections.testing', [
            'driver' => 'sqlite',
            'database' => ':memory:',
        ]);
    }
}

Orchestra Testbench provides a minimal Laravel environment for testing. Your tests extend this base class, which loads your Service Provider and sets up a testing database. This ensures your package works in real Laravel applications.

Publishing Assets Lets Users Customize Your Package

// In your Service Provider's boot() method
$this->publishes([
    __DIR__.'/../config/mypackage.php' => config_path('mypackage.php'),
], 'config');

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

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

Publishing copies files from your package to the user's Laravel application. Users run php artisan vendor:publish --provider=YourServiceProvider --tag=config to copy your config file to their app, where they can modify it. This balances sensible defaults with customization.

Distribution Through Packagist Makes Installation One Command

  1. Push your package to GitHub/GitLab with proper versioning tags
  2. Submit to Packagist.org (it's free and automated)
  3. Users install with composer require vendor/package-name
  4. Laravel auto-discovers and loads your Service Provider

Packagist syncs with your Git repository. When you push a new tag (git tag v1.0.0 && git push --tags), Packagist automatically creates a new release. Semantic versioning (1.0.0, 1.1.0, 2.0.0) helps users understand compatibility.

Advanced Features: Commands, Facades, and Configuration

// Artisan Commands
protected $commands = [
    Commands\MyPackageCommand::class,
];

// Facades for static access
class MyPackageFacade extends Facade
{
    protected static function getFacadeAccessor()
    {
        return MyService::class;
    }
}

// Configuration merging
$this->mergeConfigFrom(__DIR__.'/../config/mypackage.php', 'mypackage');

Commands let users interact with your package via php artisan. Facades provide static access (MyPackage::doSomething()). Configuration merging combines your defaults with user settings, ensuring your package works even if users don't publish your config file.


Transform any Laravel functionality into a reusable package following these patterns. Start small, test thoroughly, and publish early.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment