Open-source project
lucasdotvin/laravel-soulbscription avatar
lucasdotvin/laravel-soulbscription

laravel-soulbscription: subscriptions, consumable features and quotas for Laravel

A straightforward interface to handle subscriptions and feature consumption.

711 stars109 forksPHPMIT

At a glance

What is it?
Laravel Soulbscription is a PHP package that models SaaS billing as plans and features, where a feature can be a boolean permission, a consumable allowance that renews on a schedule, a postpaid meter, or a quota, and the package keeps track of what each user has consumed. It suits Laravel teams whose pricing is not one flat monthly fee, and the interesting part of the design is that features carry their own periodicity independently of the plan they sit on.
Who is it for?
Adopt laravel-soulbscription if your Laravel billing model mixes a plan with per-plan allowances, because it separates the plan from the feature and lets a feature carry its own renewal period, which is the case a single subscription table handles badly.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 123 days ago.
What is it written in?
Mainly PHP, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 23, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Plans on one side, features on the other

Laravel Soulbscription describes itself as a straightforward interface to handle subscriptions and features consumption, and the README's own emphasis is that it is an interface, not a payment gateway. There is no Stripe, no Paddle and no invoice rendering anywhere in what is documented. What the package does is model the shape of what a customer has bought and what they have used, and leave the money movement to something else.

The model has two halves. A plan is what someone subscribes to, and a feature is something they can consume. The README makes the split explicit when it notes that both plans and consumable features have their own periodicity, so a user can have a monthly plan with weekly features. That sentence is the design in miniature.

The second half is what makes the package more than a subscription table. Consumption is tracked per feature per user, and a feature can be one of several kinds, each with different consumption semantics. A boolean feature, a consumable allowance, a postpaid meter and a quota are four different things, and the package gives each its own model.

The PHP side of the project is set up in a way that is easy to read from the repository listing. There is a `src/` directory for the package code, `config/` for the configuration, `database/` for migrations and seeders, `tests/`, a `composer.json`, a `CHANGELOG.md` and a `LICENSE.md`. The tooling is thorough: a phpunit configuration, a phpstan configuration with a baseline file, a phpcs configuration, a `.php_cs.dist.php` for the style fixer, and a docker-compose file that runs the composer and php services for development.

The licence is MIT, the last push was on 2026-06-22, and the latest listed releases are v4.3.2 and v4.3.1, both from 2025-07-24, with 4.3.0 from 2025-07-22.

Installing, and the migrations that have to be published by hand

Installation is a composer require and the package migrations are loaded automatically, but you can still publish them:

bash
composer require lucasdotvin/laravel-soulbscription
bash
php artisan vendor:publish --tag="soulbscription-migrations"
php artisan migrate

The automatic loading is the convenience path, and the publish command exists so you can read and edit the migrations before they run. That is a reasonable design for a package that owns a schema.

The upgrade path is the part to notice. If you already use the package and need to move to a newer version, you must publish the upgrade migrations, and the command is tagged by the version range it bridges:

bash
php artisan vendor:publish --tag="soulbscription-migrations-upgrades-1.x-2.x"
php artisan migrate

Each major transition appears to get its own tag and its own set of migrations, and the README points at the upgrades folder in the repository for the list of what is available. So an installation that has been upgraded before will accumulate upgrade migrations one range at a time, and skipping one is not a trivial mistake.

The alternative to a migration-per-range would be a single schema rebuild or a squashed baseline, but neither suits a package that has been through four major versions and wants an existing user to be able to step from an old release to a current one without a data dump.

The final piece of setup is the trait. To start using it you add the given trait to your `User` model, or to any entity you want to have subscriptions:

php
<?php

namespace App\Models;

use LucasDotVin\Soulbscription\Models\Concerns\HasSubscriptions;

class User
{
    use HasSubscriptions;
}

And that's it, as the README puts it. The trait is the whole integration surface, and the fact that it can be applied to any entity rather than only to a User model is what lets the same package sit on a team, a project or a machine in a multi-tenant application.

Consumable features and what periodicity means

Features are defined first, in a seeder, and the example in the README creates two of them.

php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use LucasDotVin\Soulbscription\Enums\PeriodicityType;
use LucasDotVin\Soulbscription\Models\Feature;

class FeatureSeeder extends Seeder
{
    public function run()
    {
        $deployMinutes = Feature::create([
            'consumable'       => true,
            'name'             => 'deploy-minutes',
            'periodicity_type' => PeriodicityType::Day,
            'periodicity'      => 1,
        ]);

        $customDomain = Feature::create([
            'consumable' => false,
            'name'       => 'custom-domain',
        ]);
    }
}

The first feature, deploy-minutes, is consumable, which the README explains means the users can use it a limited number of times or until a given amount. The `PeriodicityType::Day` and the `1` say it should be renewed every day, so a user could spend his minutes today and have them back tomorrow.

The second feature, custom-domain, is not consumable, which implies only that the users with access to it can perform a given action, in this case use a custom domain. That is the boolean case, and it is a first class concept rather than a special case of a counter.

The periodicity values come from a `PeriodicityType` enum, and the combination of a type and a number is what makes the schedule expressible. A daily renewal, a weekly renewal, a monthly renewal and a yearly renewal are the same shape with a different enum value, and the `1` is how many periods of that type make up the cycle.

The docblock note about the two plans and the consumable features both having periodicity is the part to carry forward. It means a plan defines when the subscription renews, and a feature defines when its allowance resets, and those two clocks are independent. A monthly plan with a daily deploy allowance is the normal case for a hosting product, and a model that stored consumption on the subscription row could not express it without a second table, which is presumably what the feature model is.

Every feature in the example is created in a seeder, so features are reference data rather than something created at runtime.

Postpaid meters and quota features for storage-like usage

Two feature kinds handle the cases where a simple allowance is the wrong model, and they are the parts of the package with the most design thought behind them.

The first is postpaid. You can set a feature so it can be used over its charges, by setting the `postpaid` attribute to true:

php
$cpuUsage = Feature::create([
    'consumable' => true,
    'postpaid'   => true,
    'name'       => 'cpu-usage',
]);

The effect is that the user will be able to use the feature until the end of the period, even if he does not have enough charges to use it, and you can charge him later. That is the shape of a cloud provider's metering: the meter reports usage and the bill arrives at the end of the period, rather than being prepaid.

The second is quota. The example given is a file storage system, where you have to increase and decrease feature consumption as users upload and delete files. Quota features have a unique, unexpirable consumption, so they can reflect a constant value:

php
class FeatureSeeder extends Seeder
{
    public function run()
    {
        $storage = Feature::create([
            'consumable' => true,
            'quota'      => true,
            'name'       => 'storage',
        ]);
    }
}

and the controller side sets the consumed value rather than incrementing it:

php
class PhotoController extends Controller
{
    public function store(Request $request)
    {
        $userFolder = auth()->id() . '-files';

        $request->file->store($userFolder);

        $usedSpace = collect(Storage::allFiles($userFolder))
            ->map(fn (string $subFile) => Storage::size($subFile))
            ->sum();

        auth()->user()->setConsumedQuota('storage', $usedSpace);

        return redirect()->route('files.index');
    }
}

The mechanism difference is the point. A consumable allowance is decremented as it is used and refilled on its periodicity, so the call is an increment. A quota is a running measurement, so the call is a set, and the caller recomputes the total from the source of truth. The README describes exactly that: store an uploaded file in a folder, calculate the folder size by retrieving all of its subfiles, and set the consumed storage quota as the directory total size.

That difference in call semantics is a real integration decision. A quota feature means the application must be able to recompute the true value, which is more work but survives the case where an increment is lost or a file is deleted outside the application.

The unexpirable part is what makes a quota different from a consumable allowance. A consumable allowance resets on its schedule, so a user's deploy minutes come back. A quota does not, so 10 GB of storage stays 10 GB until the plan changes.

Plans, free plans and where the pricing logic lives

Plans are defined in a seeder too, in the same shape as features:

php
<?php

namespace Database\Seeders;

use Illuminate\Database\Seeder;
use LucasDotVin\Soulbscription\Enums\PeriodicityType;
use LucasDotVin\Soulbscription\Models\Plan;

class PlanSeeder extends Seeder
{
    public function run()
    {
        $silver = Plan::create([
            'name'             => 'silver',
            'periodicity_type' => PeriodicityType::Month,
            'periodicity'      => 1,
        ]);

        $gold = Plan::create([
            'name'             => 'gold',
            'periodicity_type' => PeriodicityType::Month,
            'periodicity'      => 1,
        ]);
    }
}

The periodicity options make the two plans monthly, as the README says. The interesting part is that the plan carries a name and a periodicity and nothing else, which tells you where the pricing lives: the difference between silver and gold is not in the plan row, it is in which features each plan grants.

Plans can also have no periodicity at all. You can define plans without periodicity so your users can subscribe to them permanently, or until they cancel their subscriptions, by passing a null value to the `periodicity_type` and `periodicity` attributes. The README calls these free plans or permanent plans, and the seeder for one looks like this:

php
$free = Plan::create([
    'name'             => 'free'

A null periodicity is how a free tier is expressed, and it is a different thing from a plan with a periodicity of one day that happens to cost nothing, because the plan never renews and never resets. A user on a free plan with a daily consumable feature still gets that feature refilled daily, while a plan with no periodicity and a consumable feature does not, which is a subtlety worth understanding before you set up a trial tier.

The model surface is small and consistent: `Feature` and `Plan` with a shared `PeriodicityType` enum, a `HasSubscriptions` trait for the subscribing entity, and a set of methods on that entity for reading and writing consumption. Everything else is ordinary Laravel.

Editorial conclusion

Adopt laravel-soulbscription if your Laravel billing model mixes a plan with per-plan allowances, because it separates the plan from the feature and lets a feature carry its own renewal period, which is the case a single subscription table handles badly. Do not adopt it if you only need a single monthly price and a boolean pro flag, where a column on your users table is enough, and check first that you read the upgrade migrations before moving an existing installation to a new major version, since the package requires them to be published and run by hand.

Frequently asked questions

What is Laravel Soulbscription?

It is a PHP package for handling subscriptions and feature consumption in Laravel. It models what a user has subscribed to and what they have consumed, without handling payments or invoicing, and it is MIT licensed.

How do I install laravel-soulbscription?

Run `composer require lucasdotvin/laravel-soulbscription`. The package migrations are loaded automatically, and you can publish them yourself with `php artisan vendor:publish --tag="soulbscription-migrations"` followed by `php artisan migrate`.

What are consumable, postpaid and quota features?

A consumable feature is a limited allowance that renews on its periodicity. A postpaid feature can be used over its charges, so the user keeps access until the end of the period even without enough charges. A quota feature has unique, unexpirable consumption that reflects a constant value, like used storage, and is set rather than incremented.

What do I have to do when upgrading laravel-soulbscription?

Publish the upgrade migrations for the version range you are crossing and run migrate, for example `php artisan vendor:publish --tag="soulbscription-migrations-upgrades-1.x-2.x"`. The available upgrade migrations are listed in the upgrades folder of the repository.

How do I integrate laravel-soulbscription with my models?

Add the HasSubscriptions trait to your User model, or to any entity you want to have subscriptions. The trait is the whole integration surface and it is not limited to the User model.

Is laravel-soulbscription still being maintained?

The repository is not archived and the last push was on 2026-06-22. The listed releases are v4.3.2 and v4.3.1 from 2025-07-24 and 4.3.0 from 2025-07-22, with continuous integration, code style, PHPStan and PHPUnit configurations in the repository.

Official sources

  1. License: MIT
  2. lucasdotvin/laravel-soulbscription on GitHub
  3. Project website
  4. README
  5. Releases
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/lucasdotvin-laravel-soulbscription.svg)](https://hysenlabs.com/projects/lucasdotvin-laravel-soulbscription)

Was this useful?