PHPackages                             keoby/laravel-plans - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Utility &amp; Helpers](/categories/utility)
4. /
5. keoby/laravel-plans

ActiveLibrary[Utility &amp; Helpers](/categories/utility)

keoby/laravel-plans
===================

Laravel Plans is a package for SaaS apps that need management over plans, features, subscriptions, events for plans or limited, countable features.

v1.0.3(3y ago)112MITPHPPHP ^8.1

Since Mar 22Pushed 3y agoCompare

[ Source](https://github.com/keoby/laravel-plans)[ Packagist](https://packagist.org/packages/keoby/laravel-plans)[ Docs](https://github.com/keoby/laravel-plans)[ GitHub Sponsors](https://github.com/abr4xas)[ RSS](/packages/keoby-laravel-plans/feed)WikiDiscussions master Synced 1mo ago

READMEChangelog (4)Dependencies (11)Versions (5)Used By (0)

Laravel Plans
-------------

[](#laravel-plans)

Laravel Plans is a package for SaaS apps that need management over plans, features, subscriptions, events for plans or limited, countable features.

### Laravel Cashier

[](#laravel-cashier)

---

While Laravel Cashier does this job really well, there are some features that can be useful for SaaS apps:

- **Countable, limited features** - If you plan to limit the amount of resources a subscriber can have and track the usage, this package does that for you.
- **Event-driven by nature** - you can listen for events. What if you can give 3 free days to the next subscription if your users pay their invoice in time?

### Installation

[](#installation)

---

Install the package:

```
$ composer require keoby/laravel-plans
```

If your Laravel version does not support package discovery, add this line in the `providers` array in your `config/app.php` file:

```
Keoby\LaravelPlans\LaravelPlansServiceProvider::class,
```

Publish the config file &amp; migration files:

```
$ php artisan vendor:publish --tag=laravel-plans-migrations
```

Migrate the database:

```
$ php artisan migrate
```

Add the `HasPlans` trait to your Eloquent model:

```
use Keoby\LaravelPlans\Traits\HasPlans;

class User extends Model {
    use HasPlans;
    ...
}
```

### Creating plans

[](#creating-plans)

---

The basic unit of the subscription-like system is a plan. You can create it using `Keoby\LaravelPlans\Models\Plan` or your model, if you have implemented your own.

```
$plan = Plan::create([
    'name' => 'Enterprise',
    'description' => 'The biggest plans of all.',
    'duration' => 30, // in days
]);
```

### Features

[](#features)

---

Each plan has features. They can be either countable, and those are limited or unlimited, or there just to store the information, such a specific permission.

Marking a feature type can be done using:

- `feature`, is a single string, that do not needs counting. For example, you can store permissions.
- `limit`, is a number. For this kind of feature, the `limit` attribute will be filled. It is meant to measure how many of that feature the user has consumed, from this subscription. For example, you can count how many build minutes this user has consumed during the month (or during the Cycle, which is 30 days in this example)

**Note: For unlimited feature, the `limit` field will be set to any negative value.**

To attach features to your plan, you can use the relationship `features()` and pass as many `Keoby\LaravelPlans\Models\Feature`instances as you need:

```
$plan->features()->saveMany([
    new Feature([
        'name' => 'Vault access',
        'code' => 'vault.access',
        'description' => 'Offering access to the vault.',
        'type' => 'feature',
    ]),
    new Feature([
        'name' => 'Build minutes',
        'code' => 'build.minutes',
        'description' => 'Build minutes used for CI/CD.',
        'type' => 'limit',
        'limit' => 2000,
    ]),
    new Feature([
        'name' => 'Users amount',
        'code' => 'users.amount',
        'description' => 'The maximum amount of users that can use the app at the same time.',
        'type' => 'limit',
        'limit' => -1, // or any negative value
    ]),
    ...
]);
```

Later, you can retrieve the permissions directly from the subscription:

```
$subscription->features()->get(); // All features
$subscription->features()->code($codeId)->first(); // Feature with a specific code.
$subscription->features()->limited()->get(); // Only countable/unlimited features.
$subscription->features()->feature()->get(); // Uncountable, permission-like features.
```

### Subscribing to plans

[](#subscribing-to-plans)

---

Your users can be subscribed to plans for a certain amount of days or until a certain date.

```
$subscription = $user->subscribeTo($plan, 30); // 30 days
$subscription->remainingDays(); // 29 (29 days, 23 hours, ...)
```

By default, the plan is marked as `recurring`, so it's eligible to be extended after it expires, if you plan to do so like it's explained in the **Recurrency** section below.

If you don't want a recurrent subscription, you can pass `false` as a third argument:

```
$subscription = $user->subscribeTo($plan, 30, false); // 30 days, non-recurrent
```

If you plan to subscribe your users until a certain date, you can pass strngs containing a date, a datetime or a Carbon instance.

If your subscription is recurrent, the amount of days for a recurrency cycle is the difference between the expiring date and the current date.

```
$user->subscribeToUntil($plan, '2018-12-21');
$user->subscribeToUntil($plan, '2018-12-21 16:54:11');
$user->subscribeToUntil($plan, Carbon::create(2018, 12, 21, 16, 54, 11));
$user->subscribeToUntil($plan, '2018-12-21', false); // no recurrency
```

**Note: If the user is already subscribed, the `subscribeTo()` will return false. To avoid this, upgrade or extend the subscription.**

### Upgrading subscription

[](#upgrading-subscription)

---

Upgrading the current subscription's plan can be done in two ways: it either extends the current subscription with the amount of days passed or creates another one, in extension to this current one.

Either way, you have to pass a boolean as the third parameter. By default, it extends the current subscription.

```
// The current subscription got longer with 60 days.
$currentSubscription = $user->upgradeCurrentPlanTo($anotherPlan, 60, true);

// A new subscription, with 60 days valability, starting when the current one ends.
$newSubscription = $user->upgradeCurrentPlanTo($anotherPlan, 60, false);
```

Just like the subscribe methods, upgrading also support dates as a third parameter if you plan to create a new subscription at the end of the current one.

```
$user->upgradeCurrentPlanToUntil($anotherPlan, '2018-12-21', false);
$user->upgradeCurrentPlanToUntil($anotherPlan, '2018-12-21 16:54:11', false);
$user->upgradeCurrentPlanToUntil($anotherPlan, Carbon::create(2018, 12, 21, 16, 54, 11), false);
```

Passing a fourth parameter is available, if your third parameter is `false`, and you should pass it if you'd like to mark the new subscription as recurring.

```
// Creates a new subscription that starts at the end of the current one, for 30 days and recurrent.
$newSubscription = $user->upgradeCurrentPlanTo($anotherPlan, 30, false, true);
```

### Extending current subscription

[](#extending-current-subscription)

---

Upgrading uses the extension methods, so it uses the same arguments, but you do not pass as the first argument a Plan model:

```
// The current subscription got extended with 60 days.
$currentSubscription = $user->extendCurrentSubscriptionWith(60, true);

// A new subscription, which starts at the end of the current one.
$newSubscription = $user->extendCurrentSubscriptionWith(60, false);

// A new subscription, which starts at the end of the current one and is recurring.
$newSubscription = $user->extendCurrentSubscriptionWith(60, false, true);
```

Extending also works with dates:

```
$user->extendCurrentSubscriptionUntil('2018-12-21');
```

### Cancelling subscriptions

[](#cancelling-subscriptions)

---

You can cancel subscriptions. If a subscription is not finished yet (it is not expired), it will be marked as `pending cancellation`. It will be fully cancelled when the expiration dates passes the current time and is still cancelled.

```
// Returns false if there is not an active subscription.
$user->cancelCurrentSubscription();
$lastActiveSubscription = $user->lastActiveSubscription();

$lastActiveSubscription->isCancelled(); // true
$lastActiveSubscription->isPendingCancellation(); // true
$lastActiveSubscription->isActive(); // false

$lastActiveSubscription->hasStarted();
$lastActiveSubscription->hasExpired();
```

### Consuming countable features

[](#consuming-countable-features)

---

To consume the `limit` type feature, you have to call the `consumeFeature()` method within a subscription instance.

To retrieve a subscription instance, you can call `activeSubscription()` method within the user that implements the trait. As a pre-check, don't forget to call `hasActiveSubscription()` from the user instance to make sure it is subscribed to it.

```
if ($user->hasActiveSubscription()) {
    $subscription = $user->activeSubscription();
    $subscription->consumeFeature('build.minutes', 10);

    $subscription->getUsageOf('build.minutes'); // 10
    $subscription->getRemainingOf('build.minutes'); // 1990
}
```

The `consumeFeature()` method will return:

- `false` if the feature does not exist, the feature is not a `limit` or the amount is exceeding the current feature allowance
- `true` if the consumption was done successfully

```
// Note: The remaining of build.minutes is now 1990

$subscription->consumeFeature('build.minutes', 1991); // false
$subscription->consumeFeature('build.hours', 1); // false
$subscription->consumeFeature('build.minutes', 30); // true

$subscription->getUsageOf('build.minutes'); // 40
$subscription->getRemainingOf('build.minutes'); // 1960
```

If `consumeFeature()` meets an unlimited feature, it will consume it and it will also track usage just like a normal record in the database, but will never return false. The remaining will always be `-1` for unlimited features.

The revering method for `consumeFeature()` method is `unconsumeFeature()`. This works just the same, but in the reverse:

```
// Note: The remaining of build.minutes is 1960

$subscription->consumeFeature('build.minutes', 60); // true

$subscription->getUsageOf('build.minutes'); // 100
$subscription->getRemainingOf('build.minutes'); // 1900

$subscription->unconsumeFeature('build.minutes', 100); // true
$subscription->unconsumeFeature('build.hours', 1); // false

$subscription->getUsageOf('build.minutes'); // 0
$subscription->getRemainingOf('build.minutes'); // 2000
```

Using the `unconsumeFeature()` method on unlimited features will also reduce usage, but it will never reach negative values.

### Events

[](#events)

---

When using subscription plans, you want to listen for events to automatically run code that might do changes for your app.

Events are easy to use. If you are not familiar, you can check [Laravel's Official Documentation on Events](https://laravel.com/docs/9.x/events).

All you have to do is to implement the following Events in your `EventServiceProvider.php` file. Each event will have it's own members than can be accessed through the `$event` variable within the `handle()` method in your listener.

```
$listen = [
    ...
    \Keoby\Plans\Events\CancelSubscription::class => [
        // $event->model = The model that cancelled the subscription.
        // $event->subscription = The subscription that was cancelled.
    ],
    \Keoby\Plans\Events\NewSubscription::class => [
        // $event->model = The model that was subscribed.
        // $event->subscription = The subscription that was created.
    ],
     \Keoby\Plans\Events\NewSubscriptionUntil::class => [
        // $event->model = The model that was subscribed.
        // $event->subscription = The subscription that was created.
    ],
    \Keoby\Plans\Events\ExtendSubscription::class => [
        // $event->model = The model that extended the subscription.
        // $event->subscription = The subscription that was extended.
        // $event->startFromNow = If the subscription is exteded now or is created a new subscription, in the future.
        // $event->newSubscription = If the startFromNow is false, here will be sent the new subscription that starts after the current one ends.
    ],
    \Keoby\Plans\Events\ExtendSubscriptionUntil::class => [
        // $event->model = The model that extended the subscription.
        // $event->subscription = The subscription that was extended.
        // $event->expiresOn = The Carbon instance of the date when the subscription will expire.
        // $event->startFromNow = If the subscription is exteded now or is created a new subscription, in the future.
        // $event->newSubscription = If the startFromNow is false, here will be sent the new subscription that starts after the current one ends.
    ],
    \Keoby\Plans\Events\UpgradeSubscription::class => [
        // $event->model = The model that upgraded the subscription.
        // $event->subscription = The current subscription.
        // $event->startFromNow = If the subscription is upgraded now or is created a new subscription, in the future.
        // $event->oldPlan = Here lies the current (which is now old) plan.
        // $event->newPlan = Here lies the new plan. If it's the same plan, it will match with the $event->oldPlan
    ],
    \Keoby\Plans\Events\UpgradeSubscriptionUntil::class => [
        // $event->model = The model that upgraded the subscription.
        // $event->subscription = The current subscription.
        // $event->expiresOn = The Carbon instance of the date when the subscription will expire.
        // $event->startFromNow = If the subscription is upgraded now or is created a new subscription, in the future.
        // $event->oldPlan = Here lies the current (which is now old) plan.
        // $event->newPlan = Here lies the new plan. If it's the same plan, it will match with the $event->oldPlan
    ],
    \Keoby\Plans\Events\FeatureConsumed::class => [
        // $event->subscription = The current subscription.
        // $event->feature = The feature that was used.
        // $event->used = The amount used.
        // $event->remaining = The total amount remaining. If the feature is unlimited, will return -1
    ],
     \Keoby\Plans\Events\FeatureUnconsumed::class => [
        // $event->subscription = The current subscription.
        // $event->feature = The feature that was used.
        // $event->used = The amount reverted.
        // $event->remaining = The total amount remaining. If the feature is unlimited, will return -1
    ],
];
```

### Testing

[](#testing)

```
composer test
```

### Changelog

[](#changelog)

Please see [CHANGELOG](CHANGELOG.md) for more information on what has changed recently.

### Contributing

[](#contributing)

Please see [CONTRIBUTING](CONTRIBUTING.md) for details.

### Security Vulnerabilities

[](#security-vulnerabilities)

Please review [our security policy](../../security/policy) on how to report security vulnerabilities.

### Credits

[](#credits)

- [Keoby](https://github.com/keoby)
- **Georgescu Alexandru** *Initial work*
- [Musa Kurt](https://github.com/whtht)
- [Dukens Thelemaque](https://github.com/whtht)
- [All Contributors](../../contributors)

### License

[](#license)

The MIT License (MIT). Please see [License File](LICENSE.md) for more information.

###  Health Score

25

—

LowBetter than 37% of packages

Maintenance20

Infrequent updates — may be unmaintained

Popularity7

Limited adoption so far

Community8

Small or concentrated contributor base

Maturity56

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 60% of commits — single point of failure

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~4 days

Total

4

Last Release

1130d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/4b8442220e2f3c34016479b42398de5399fd71538c970ba3e0ea20d5998db421?d=identicon)[keoby](/maintainers/keoby)

---

Top Contributors

[![abr4xas](https://avatars.githubusercontent.com/u/405484?v=4)](https://github.com/abr4xas "abr4xas (9 commits)")[![bootstrap-vue-bot](https://avatars.githubusercontent.com/u/24793729?v=4)](https://github.com/bootstrap-vue-bot "bootstrap-vue-bot (6 commits)")

---

Tags

laravellaravel-planskeoby

###  Code Quality

TestsPest

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/keoby-laravel-plans/health.svg)

```
[![Health](https://phpackages.com/badges/keoby-laravel-plans/health.svg)](https://phpackages.com/packages/keoby-laravel-plans)
```

###  Alternatives

[hyn/multi-tenant

Run multiple websites using the same laravel installation while keeping tenant specific data separated for fully independant multi-domain setups.

2.6k1.1M9](/packages/hyn-multi-tenant)[livewire/volt

An elegantly crafted functional API for Laravel Livewire.

4195.3M84](/packages/livewire-volt)[ronasit/laravel-helpers

Provided helpers function and some helper class.

1475.7k13](/packages/ronasit-laravel-helpers)[forxer/laravel-gravatar

A library providing easy gravatar integration in a Laravel project.

4235.6k](/packages/forxer-laravel-gravatar)[foryoufeng/laravel-generator

A tool for generate Laravel code file

712.8k](/packages/foryoufeng-laravel-generator)[iteks/laravel-enum

A comprehensive Laravel package providing enhanced enum functionalities, including attribute handling, select array conversions, and fluent facade interactions for robust enum management in Laravel applications.

2516.7k](/packages/iteks-laravel-enum)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
