How to Create Custom Packages in Laravel 12: A Complete Step-by-Step Guide
Learn how to build, test and publish your own custom Laravel 12 packages with this developer-friendly tutorial. Master package development to enhance code reusability and contribute to the Laravel ecosystem. Follow our expert step-by-step process from initial setup to Packagist submission.
What You'll Learn
- Setting up a package development environment
- Creating the package structure
- Implementing service providers
- Adding controllers, models, and migrations
- Publishing assets and configurations
- Testing your package
- Submitting to Packagist'
Prerequisites
- PHP 8.2 or higher
- Composer installed
- Basic knowledge of Laravel framework
- Laravel 12 project for testing
Step 1: Set Up Your Development Environment
First, create a new Laravel 12 project where you'll develop and test your package:
composer create-project laravel/laravel laravel-package-dev
cd laravel-package-dev
Note: Ok, Now In the below part I am using CodeHunger as the packages list and Learning as the Package name, for example, the directory structure will be like this packages/YourName/yourpackage/src , then in below example it will be packages/CodeHunger/Learning/src
Step 2: Create Package Structure
Laravel packages follow a specific directory structure. Create this structure in your project's root directory:
mkdir -p packages/CodeHunger/Learning/src
Now, Create the below folder under your src Folder:
packages/
└── CodeHunger/
└── Learning/
└── src/
├── Console/
│ └── Commands/
├── Config/
├── Controllers/
├── Database/
│ ├── migrations/
│ └── seeders/
│ └── LearningSeeder.php
├── Http/
│ ├── Controllers/
│ └── Middleware/
├── Models/
│ └── Learning.php
├── Providers/
├── Resources/
│ ├── views/
│ └── lang/
├── Routes/
└── Support/
Step 3: Create Service Provider
The service provider is the entry point of your package. Create a file named LearningServiceProvider.php
in the src
directory:
<?php
namespace CodeHunger\Learning\Provider;
use Illuminate\Support\ServiceProvider;
class LearningServiceProvider extends ServiceProvider
{
public function boot(): void
{
// Load routes
$this->loadRoutesFrom(__DIR__ . '/../Routes/web.php');
// Load migrations
$this->loadMigrationsFrom(__DIR__ . '/../Database/migrations');
// Load views with a namespace
$this->loadViewsFrom(__DIR__ . '/../Resources/views', 'learning');
// Optional: Publish assets or configs (currently commented)
$this->vendorPublish();
}
public function register(): void
{
// Bind classes or services into the container here if needed
}
protected function vendorPublish(): void
{
// Example for asset publishing (uncomment and customize if needed)
/*
$this->publishes([
__DIR__ . '/../resources/assets/js' => public_path('vendor/learning/js'),
], 'learning-assets');
*/
}
}
Step 4: Add the package path to the composer.json in your autoload json array and in the extra array
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"CodeHunger\\Learning\\": "packages/CodeHunger/Learning/src/"
}
},
"extra": {
"laravel": {
"providers": [
"CodeHunger\\Learning\\Provider\\LearningServiceProvider"
]
}
Full Composer.json code will looks like the below one
{
"$schema": "https://getcomposer.org/schema.json",
"name": "laravel/laravel",
"type": "project",
"description": "The skeleton application for the Laravel framework.",
"keywords": ["laravel", "framework"],
"license": "MIT",
"require": {
"php": "^8.2",
"laravel/framework": "^12.0",
"laravel/tinker": "^2.10.1"
},
"require-dev": {
"fakerphp/faker": "^1.23",
"laravel/pail": "^1.2.2",
"laravel/pint": "^1.13",
"laravel/sail": "^1.41",
"mockery/mockery": "^1.6",
"nunomaduro/collision": "^8.6",
"phpunit/phpunit": "^11.5.3"
},
"autoload": {
"psr-4": {
"App\\": "app/",
"Database\\Factories\\": "database/factories/",
"Database\\Seeders\\": "database/seeders/",
"CodeHunger\\Learning\\": "packages/CodeHunger/Learning/src/"
}
},
"autoload-dev": {
"psr-4": {
"Tests\\": "tests/"
}
},
"scripts": {
"post-autoload-dump": [
"Illuminate\\Foundation\\ComposerScripts::postAutoloadDump",
"@php artisan package:discover --ansi"
],
"post-update-cmd": [
"@php artisan vendor:publish --tag=laravel-assets --ansi --force"
],
"post-root-package-install": [
"@php -r \"file_exists('.env') || copy('.env.example', '.env');\""
],
"post-create-project-cmd": [
"@php artisan key:generate --ansi",
"@php -r \"file_exists('database/database.sqlite') || touch('database/database.sqlite');\"",
"@php artisan migrate --graceful --ansi"
],
"dev": [
"Composer\\Config::disableProcessTimeout",
"npx concurrently -c \"#93c5fd,#c4b5fd,#fb7185,#fdba74\" \"php artisan serve\" \"php artisan queue:listen --tries=1\" \"php artisan pail --timeout=0\" \"npm run dev\" --names=server,queue,logs,vite"
]
},
"extra": {
"laravel": {
"providers": [
"CodeHunger\\Learning\\Provider\\LearningServiceProvider"
]
}
},
"config": {
"optimize-autoloader": true,
"preferred-install": "dist",
"sort-packages": true,
"allow-plugins": {
"pestphp/pest-plugin": true,
"php-http/discovery": true
}
},
"minimum-stability": "stable",
"prefer-stable": true
}
Step 5: Add your service provider path at App/Providers
<?php
namespace App\Providers;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
$this->app->register(\CodeHunger\Learning\Provider\LearningServiceProvider::class);
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
//
}
}
Step 6: Add Routes
Create a routes
directory and add routes for your package, only run the below command If you haven't created the routes folder
Create routes/web.php
:
<?php
use Illuminate\Support\Facades\Route;
use CodeHunger\Learning\Http\Controllers\LearningController;
Route::group(['prefix' => 'learning',], function () {
Route::get('/', [LearningController::class, 'index'])->name('learning.index');
});
Step 6: Create Controllers
Create a controller directory structure and add a controller:
bash
mkdir -p src/Http/Controllers
Create src/Http/Controllers/YourController.php
:
<?php
namespace CodeHunger\Learning\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Routing\Controller;
class LearningController extends Controller
{
public function index()
{
return view('learning::index');
}
}
Step 7: Add Views
Create a views directory for your package:
Create resources/views/index.blade.php
:
<html>
<head>
<title>CodeHunger</title>
<link rel="stylesheet" href="{{ asset('css/app.css') }}">
</head>
<body>
<h1>Welcome to CodeHunger</h1>
</body>
</html>
Step 8: Add Database Migrations
If your package needs database tables:
mkdir -p database/migrations
Create database/migrations/0000_00_00_000000_create_your_table.php
:
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
/**
* Run the migrations.
*/
public function up(): void
{
Schema::create('learnings', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->text('description')->nullable();
$table->timestamps();
});
}
/**
* Reverse the migrations.
*/
public function down(): void
{
Schema::dropIfExists('your_table');
}
};
Step 9: Create Models
If your package works with database models:
Create src/Models/YourModel.php
:
namespace CodeHunger\Learning\\Models;
use Illuminate\Database\Eloquent\Model;
class Learning extends Model
{
protected $fillable = [
'name',
'description',
];
}
Step:10 Check the created route
First, run the below command in your command panel
Then visit the below URL in your browser
You can see the output like the below image

To assist with implementation, you can directly download the complete source code referenced in this guide from the official GitHub repository:
🔗 GitHub Repository: https://github.com/codehunger-team/custom-package
This repository includes all the necessary structure and files required to build and register a Laravel 12 custom package, as detailed in the blog. Using this codebase ensures consistency with the tutorial and allows for quicker integration into your Laravel application.
Conclusion
Creating custom packages in Laravel 12 is a powerful way to modularize your code and share functionality across projects. By following this guide, you can build well-structured, maintainable packages that integrate seamlessly with Laravel's ecosystem. As you become more experienced with package development, you'll discover more advanced techniques to enhance your packages and contribute to the Laravel community. Remember that the best packages solve specific problems elegantly while maintaining compatibility with Laravel's design principles and philosophy.