How to create custom Laravel packages

What is a custom package? A custom package is a reusable piece of code that can be used across different projects to avoid code duplica...

Karan Datwani
Karan Datwani
Share:

What is a custom package?

A custom package is a reusable piece of code that can be used across different projects to avoid code duplication and repetition. Think of it as a mini application that you can easily plug into your main application and get it going.

Why build a custom package?

  • Code Reusability: Bundle related features and reuse them across multiple applications. This results in better code management and saves development time.
  • Simplified Maintenance: Fix bugs and update features in one central place, rather than in each individual application.
  • Modularity: Organize your app into modular components for better structure and understanding.
  • Testing & Quality Assurance: You can independently test the packages before integrating them into your main project.

When to NOT create a custom package

  • If you're only using the code in a single application, it's better to use a local helper or service class.
  • If you have no plan to maintain the package or update it regularly, skip packaging.
  • If there's a popular third-party package that already does what you need—use it instead.
  • If you're creating packages but only one app uses them, you'll likely slow down your development process with unnecessary overhead.
  • If you find yourself prematurely abstracting code without a real reuse case—you're likely over-engineering.

👉 Rule of thumb: Package it when reuse becomes real. Not before.

Helpful Tools

I'll walk you through how to manually create a package so you understand the internals. But once you're familiar, you can use these tools to speed things up:

These tools can scaffold about 80% of the boilerplate work.

Enough of theory :)

Lets dive into the actual process

1. Creating package directory

Create a directory for packages. Laravel packages typically reside in Packages directory. Here, I am taking an example of creating a custom package to show weather description based on city input given by the user.

The package directory structure should look like below:

Packages/
└── PankajVasnani/
    └── WeatherForecast/
        ├── src/
        │   ├── WeatherInfoServiceProvider.php
        │   ├── WeatherService.php
        ├── config/
        │   ├── weatherforecast.php
        ├── composer.json

2. Define the package functionality

2.1 Create Package Service Provider

Next step is to create a service provider for it where i register package's services and other configurations required for our package.

It has two methods :-

  • register() - This method helps you set up everything your application needs to function, which includes register classes and other configs.
  • boot() - This method helps you perform the actual functions after all services are successfully registered.
<?php

namespace PankajVasnani\WeatherForecast;

use Illuminate\Support\ServiceProvider;

class WeatherForecastServiceProvider extends ServiceProvider
{
    public function boot()
    {
        $this->publishes([
            __DIR__.'/../config/weatherforecast.php' => config_path('weatherforecast.php'),
        ], 'config');
    }

    public function register()
    {
        $this->mergeConfigFrom(
            __DIR__.'/../config/weatherforecast.php',
            'weatherforecast'
        );

        $this->app->singleton(WeatherService::class, function ($app) {
            return new WeatherService();
        });
    }
}

Here's what above code is doing:

  • It will publish the configuration file(weatherforecast.php) to the main application config directory so user can modify it after installation.
  • It will merge the package's default config with main app config.
  • It registers only one single instance of WeatherService which is shared across the application.

2.2. Add the Package Logic

Now its time to create the actual functionality of our package which is to tell the weather based on the input city by the end user.

<?php

namespace PankajVasnani\WeatherForecast;

class WeatherService
{
    /**
     * Get the weather forecast for the specified city.
     * Returns mock data for demonstration.
     *
     * @param string|null $city
     * @return array
     */
    public function getForecastByCity(?string $city = null): array
    {
        // Take city input by the user
        if (!$city) {
            $city = config('weatherforecast.default_city');
        }
        
        // Retrieve API token from config
        $apiToken = config('weatherforecast.api_token');
        
        // Call the mock weather API with authentication header
        $response = Http::withHeaders([
            'Authorization' => "Bearer {$apiToken}",
        ])->get("https://api.mockweather.com/forecast?city={$city}");

        // Return the response data
        return $response->json();
    }
}

Here's what above code is doing:

  • We take city as input, if not given, we take value from package's config file.
  • We call weather api with city and api token to get the weather details.
  • Then, we return the response which contains the weather information of the city.

Note: I have taken mock weather api. You can replace it with actual weather forecast API and token.

2.3 Create package config

Now, lets define the package's config that contains the API token and the default city, that will be used by weather forecast api to gather weather details about the particular city.

<?php

return [
    'api_key' => env('WEATHER_API_KEY', 'your-default-api-key'),
    'default_city' => 'New York',
];

2.4 Create composer.json file

Next, I define the package's composer.json which lists all important details about package information.

{
    "name": "pankaj-vasnani/weather-forecast",
    "description": "A simple weather forecasting Laravel package",
    "type": "library",
    "license": "MIT",
    "autoload": {
      "psr-4": {
        "PankajVasnani\\WeatherForecast\\": "src/"
      }
    },
    "extra": {
      "laravel": {
        "providers": [
          "PankajVasnani\\WeatherForecast\\WeatherForecastServiceProvider"
        ]
      }
    },
    "require": {},
    "minimum-stability": "dev",
    "prefer-stable": true
  }

Here's what above file means :-

  • PankajVasnani\\WeatherForecast\\ Maps this namespace to the src/ directory so Laravel can automatically detect and load the required classes from src/ folder.
  • The extra section contains Laravel-specific configurations, which loads WeatherForecastServiceProvider as a service provider that automatically loads the package's functionality when application starts.

Now, the most exciting part, how to use the package in main laravel application. Let's have a look.

3. Using custom package in Laravel Application

3.1 Include package URL in main application's composer.json file

Define repositories section in main application's composer.json file, that tells where the custom packages can be found. It has two keys:

  • type - This setting specifies that where the package is located. Like It's value path defines that it is in a local directory.
  • url - It defines the exact path of our package where it is located within our main application.
"repositories": [
        {
            "type": "path",
            "url": "app/Packages/PankajVasnani/WeatherForecast"
        }
    ]

Note: Make sure you always use relative path, not abolute path. Absolute path might break if you decide to move your project to different directory, environments or other machines.

3.2 Install the package using composer

Now lets proceed to install the package using composer which is a simple and straight forward process.

composer require pankaj-vasnani/weather-forecast:@dev

Here's what above command does:

  • Installs the package as a dependency in our main laravel application.
  • The use of @dev at the end means that the specified package is used for development and testing purpose, before we release the stable/production version.

3.3 Publish the package's config file

Now we publish the package's config file in laravel main application.

php artisan vendor:publish --provider="PankajVasnani\WeatherForecast\WeatherForecastServiceProvider" --tag=config

Here's what above command does:

  • php artisan vendor:publish publishes the config file from the package in main laravel application.
  • --provider="PankajVasnani\WeatherForecast\WeatherForecastServiceProvider" specifies the service provider from where to publish the files.
  • --tag=config publishes only the configuration files tagged as config.

3.4 Define controller and route for using our package

Next and the last step, we define the controller and route where we use our package's functionality. It is very easy to understand. No complex coding. Let me show you.

Route file

<?php

use Illuminate\Support\Facades\Route;
use App\Http\Controllers\WeatherForecastController;

Route::get('/weather-forecast/{city}', [WeatherForecastController::class, 'getForecastByCity']);

Controller file

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use PankajVasnani\WeatherForecast\WeatherService;

class WeatherForecastController extends Controller
{
    private $weatherService;

    public function __construct(WeatherService $weatherService)
    {
        // Constructor injection of the WeatherService
        $this->weatherService = $weatherService;
    }

    /**
     * Get the weather forecast for a specific city.
     *
     * @param string|null $city
     * @return \Illuminate\Http\JsonResponse
     */
    public function getForecastByCity(?string $city = "Jaipur") 
    {
        $forecast = $this->weatherService->getForecastByCity($city);

        return response()->json($forecast);
    }
}


Want to Build Packages for Backpack Admin Panel?

If you're planning to build a package for Backpack for Laravel—an admin panel that saves developers countless hours—you can check out how to create Backpack add-ons. It follows a similar package structure, but is tailored to integrate with Backpack’s ecosystem. Perfect if you're looking to level up your Laravel toolset.

Final Thoughts

  • Don’t package too early—wait until reuse is clear.
  • Learn the manual process to understand how things work under the hood. use skeletons later for speed.
  • Custom packages can dramatically reduce duplicate work and improve maintainability.

Build once, use everywhere! — your next innovative solution is waiting to be discovered!

Want me to turn this into a shareable GitHub Package? Let me know in the comments below.

Want to receive more articles like this?

Subscribe to our "Article Digest". We'll send you a list of the new articles, every week, month or quarter - your choice.

Reactions & Comments

What do you think about this?

Latest Articles

Wondering what our community has been up to?