Laravel Package Creation: The Complete TL;DR Guide
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
- Push your package to GitHub/GitLab with proper versioning tags
- Submit to Packagist.org (it's free and automated)
- Users install with
composer require vendor/package-name
- Laravel auto-discovers and loads your Service Provider
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.