Master the art of building reusable packages in Laravel. Learn to structure your code, register service providers, and publish assets for modular apps.
Previously in this course, we explored using traits for code reuse to handle cross-cutting concerns within a single codebase. While traits are excellent for sharing methods across models, they fall short when you need to share complex logic, migrations, or views across multiple distinct projects.
This lesson moves us into the realm of true modularity: building internal Laravel packages. By extracting shared functionality into standalone packages, you transform your codebase from a monolithic block into a collection of swappable, maintainable modules.
A Laravel package is essentially a mini-application that lives inside a dedicated directory (or a separate repository). It contains its own service providers, routes, migrations, and views. The goal is to isolate a specific domain—like our project board’s notification system—so it can be tested and updated in total isolation from the main project.
In our project board, we’ve reached a point where our task-assignment logic is getting complex. Instead of cluttering our core application, we’ll extract this into an internal package. A standard structure looks like this:
TEXTpackages/ └── Acme/ └── TaskManager/ ├── src/ │ ├── TaskManagerServiceProvider.php │ ├── Http/ │ └── Models/ ├── database/ │ └── migrations/ ├── config/ │ └── task-manager.php └── composer.json
The heart of any package is the Service Provider. It tells Laravel how to register the package's services, load migrations, and publish assets. Unlike your standard AppServiceProvider, this one is dedicated solely to the package's lifecycle.
Create your provider inside the src/ directory:
PHPnamespace Acme\TaskManager; use Illuminate\Support\ServiceProvider; class TaskManagerServiceProvider extends ServiceProvider { public function boot() { #6A9955">// Load migrations $this->loadMigrationsFrom(__DIR__.'/../database/migrations'); #6A9955">// Publish config files $this->publishes([ __DIR__.'/../config/task-manager.php' => config_path('task-manager.php'), ], 'config'); } public function register() { #6A9955">// Bind classes to the service container $this->app->singleton(TaskManager::class, function ($app) { return new TaskManager(); }); } }
To make Laravel aware of your new package, you don't need to manually register it in config/app.php if you are using Laravel 11+. If you are on an older version, add the provider to your providers array. For internal packages, use Composer's autoload section in your root composer.json to map your package's namespace:
JSON"autoload": { "psr-4": { "App\\": "app/", "Acme\\TaskManager\\": "packages/Acme/TaskManager/src/" } }
One of the most powerful features of packages is the ability to share configuration and views with the host application. The publishes method we used in the service provider is your primary tool here.
When a user runs php artisan vendor:publish --tag=config, Laravel copies your package’s configuration file into the application's config/ directory. This allows the end-user to override your defaults without touching the package code itself—a critical design pattern for maintainable applications.
For our project board, I want you to extract the "Task Priority" logic.
packages/Acme/PriorityManager directory.composer.json inside it.PriorityService into the container.priorities table and ensure it loads via loadMigrationsFrom in your provider.composer dump-autoload to refresh your application's class mapping.__DIR__ or base_path() when referencing package files. Never assume the package is located at a specific relative path from the root.Acme\). Don't just call it TaskManager.Building packages provides the ultimate form of modularity. By encapsulating logic, managing service providers, and defining clear asset publication paths, you ensure your project board remains flexible. You've now learned how to move from simple traits to full-fledged packages, which is the final step in architecting a truly professional, scalable Laravel codebase.
Now that you have the modularity required for a large-scale application, we will focus on maintaining high performance by exploring Performance Profiling tools to ensure your new packages aren't introducing bottlenecks.
Stop passing associative arrays through your application. Learn to use DTOs to enforce type safety and data integrity across your Laravel architecture.
Read moreMaster the Laravel service container by binding interfaces to implementations and using conditional binding to build decoupled, testable, and robust applications.
Building Reusable Packages