PHPackages                             michael-lurquin/feature-limiter - 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. [Payment Processing](/categories/payments)
4. /
5. michael-lurquin/feature-limiter

ActiveLibrary[Payment Processing](/categories/payments)

michael-lurquin/feature-limiter
===============================

A Laravel package to manage SaaS features, quotas and usage. Supports boolean features, numeric limits, storage, unlimited plans and period-based usage. Built for modern multi-tenant SaaS and flexible billing systems.

v1.0.6(3mo ago)1101↓50%MITPHPPHP ^8.2CI passing

Since Jan 12Pushed 3mo agoCompare

[ Source](https://github.com/michael-lurquin/feature-limiter)[ Packagist](https://packagist.org/packages/michael-lurquin/feature-limiter)[ Docs](https://github.com/michael-lurquin/feature-limiter)[ RSS](/packages/michael-lurquin-feature-limiter/feed)WikiDiscussions main Synced 1mo ago

READMEChangelog (6)Dependencies (4)Versions (8)Used By (0)

Feature Limiter for Laravel
===========================

[](#feature-limiter-for-laravel)

A flexible **plan / feature / quota / usage** system for Laravel applications, with built-in helpers for **pricing pages** (cards &amp; comparison tables).

Supports:

- Plan &amp; Feature management
- Quotas per plan
- Usage tracking per billable (User, Tenant, Team, etc.)
- Unlimited features
- Storage units (e.g. `500MB`, `1GB`)
- Period-based resets (daily, weekly, monthly, yearly, lifetime)
- Transaction-safe consumption
- Pluggable billing providers (Cashier, manual, fake, etc.)
- Pricing catalog generation (cards &amp; comparison tables)

---

Installation
------------

[](#installation)

```
composer require michael-lurquin/feature-limiter
```

Publish the config and run migrations:

```
php artisan vendor:publish --tag=feature-limiter-config
php artisan migrate
```

---

Concepts
--------

[](#concepts)

TermMeaning**Plan**A subscription plan (e.g. Starter, Pro)**Feature**A capability (sites, storage, custom\_code, etc.)**Quota**The limit defined by the plan**Usage**How much a billable has consumed**Billable**Any model or object with an `id`**Reset period**When usage is reset (none, daily, weekly, monthly, yearly)---

Creating Plans
--------------

[](#creating-plans)

```
FeatureLimiter::plan('starter')
    ->name('Starter')
    ->description('To begin with...')
    ->sort(0)
    ->active(true)
    ->monthly(9.99)
    ->yearly(129)
    ->save();
```

Short version:

```
FeatureLimiter::plan('starter')
    ->name('Starter') // Optional (ucfirst on 'key' column : starter => Starter)
    ->save();
```

### Multiple plans:

[](#multiple-plans)

```
FeatureLimiter::plans([
    'free' => ['sort' => 0],
    'starter' => ['sort' => 1],
    'comfort' => ['sort' => 2],
    'pro' => ['name' => 'Gold', 'sort' => 3, 'description' => 'To begin with...', 'price_monthly' => 9.99, 'price_yearly' => 129],
    'enterprise' => ['sort' => 4, 'active' => false],
])->save();
```

Sort version:

```
FeatureLimiter::plans([
    'free',
    'starter',
    'comfort',
    'pro',
    'enterprise',
])->save();
```

---

Creating Features
-----------------

[](#creating-features)

```
FeatureLimiter::feature('sites')
    ->name('Sites')
    ->description('Number of sites you can create')
    ->group('create-design')
    ->type(FeatureType::INTEGER)
    ->unit('sites')
    ->reset(ResetPeriod::NONE) // none|daily|weekly|monthly|yearly
    ->sort(0)
    ->active(true)
    ->save();
```

Short version:

```
FeatureLimiter::feature('sites')
    ->name('Sites') // Optional (ucfirst on 'key' column : sites => Sites)
    ->type(FeatureType::INTEGER)
    ->save();
```

Supported feature types:

- `FeatureType::INTEGER`
- `FeatureType::BOOLEAN`
- `FeatureType::STORAGE`

### Multiple features:

[](#multiple-features)

```
FeatureLimiter::features([
    'sites' => ['sort' => 0, 'type' => FeatureType::INTEGER],
    'storage' => ['sort' => 1, 'type' => FeatureType::STORAGE],
    'custom_code' => ['name' => 'Custom Code', 'type' => FeatureType::BOOLEAN, 'sort' => 2],
])->save();
```

Short version:

```
FeatureLimiter::features([
    'sites' => ['type' => FeatureType::INTEGER],
    'storage' => ['type' => FeatureType::STORAGE],
    'custom_code' => ['name' => 'Custom Code', 'type' => FeatureType::BOOLEAN],
])->save();
```

---

Assigning Features to a Plan (Quotas)
-------------------------------------

[](#assigning-features-to-a-plan-quotas)

### Integer quota

[](#integer-quota)

```
FeatureLimiter::grant('starter')
    ->feature('sites')
    ->quota(3);
```

### Boolean features

[](#boolean-features)

```
FeatureLimiter::grant('starter')->feature('custom_code'); // enabled by default
FeatureLimiter::grant('starter')->feature('custom_code')->enabled();
FeatureLimiter::grant('starter')->feature('library')->disabled();
```

### Unlimited features

[](#unlimited-features)

```
FeatureLimiter::grant('pro')->feature('storage')->unlimited();
```

Alternative unlimited values:

```
->value(null)
->value(-1)
->value('unlimited')
```

---

Assign Multiple Features at Once
--------------------------------

[](#assign-multiple-features-at-once)

```
FeatureLimiter::grant('starter')->features([
    'sites' => 3,
    'page' => 30,
    'custom_code' => false,
    'storage' => '1GB',
]);
```

Unlimited:

```
FeatureLimiter::grant('pro')->features([
    'storage' => 'unlimited',
]);
```

---

Reading Plan Quotas
-------------------

[](#reading-plan-quotas)

```
FeatureLimiter::viewPlan('starter')->quota('sites');
FeatureLimiter::viewPlan('starter')->enabled('custom_code');
FeatureLimiter::viewPlan('starter')->disabled('custom_code');
FeatureLimiter::viewPlan('pro')->unlimited('storage');
FeatureLimiter::viewPlan('pro')->value('storage');
```

---

Reading Quotas for a Billable
-----------------------------

[](#reading-quotas-for-a-billable)

```
FeatureLimiter::for($billable)->quota('sites');
FeatureLimiter::for($billable)->enabled('custom_code');
FeatureLimiter::for($billable)->disabled('custom_code');
FeatureLimiter::for($billable)->unlimited('storage');
FeatureLimiter::for($billable)->value('storage');
```

---

Usage (Consumption Tracking)
----------------------------

[](#usage-consumption-tracking)

```
FeatureLimiter::for($billable)->usage('sites');
FeatureLimiter::for($billable)->incrementUsage('sites');
FeatureLimiter::for($billable)->decrementUsage('sites');
FeatureLimiter::for($billable)->setUsage('sites', 10);
FeatureLimiter::for($billable)->clearUsage('sites');
```

### Consuming quota (non-strict)

[](#consuming-quota-non-strict)

```
FeatureLimiter::for($billable)->consume('sites', 1);
// returns false if not enough quota
```

### Consuming quota (strict)

[](#consuming-quota-strict)

```
FeatureLimiter::for($billable)->consume('storage', '500MB', strict: true);
// throws QuotaExceededException if quota is exceeded
```

### Convenience aliases

[](#convenience-aliases)

```
FeatureLimiter::for($billable)->consumeOrFail('sites', 1);
FeatureLimiter::for($billable)->consumeManyOrFail([
    'sites' => 1,
    'storage' => '500MB',
]);
```

### Refund / rollback usage

[](#refund--rollback-usage)

```
FeatureLimiter::for($billable)->refund('sites', 1);
FeatureLimiter::for($billable)->refundMany([
    'sites' => 1,
    'storage' => '500MB',
]);
```

---

Quota vs Usage
--------------

[](#quota-vs-usage)

Check if a billable can still consume quota:

```
FeatureLimiter::for($billable)->canConsume('sites', 1);
FeatureLimiter::for($billable)->canConsume('storage', '500MB');
```

Remaining quota:

```
FeatureLimiter::for($billable)->remainingQuota('sites');
```

Exceeded quota:

```
FeatureLimiter::for($billable)->exceededQuota('sites', 1);
```

Multiple features at once:

```
FeatureLimiter::for($billable)->canConsumeMany([
    'sites' => 1,
    'storage' => '500MB',
]);

FeatureLimiter::for($billable)->remainingQuotaMany([
    'sites',
    'storage',
]);
```

---

Storage Units
-------------

[](#storage-units)

Supported formats:

```
500B
1024KB
500MB
1GB
1.5GB

```

---

Billing Providers
-----------------

[](#billing-providers)

You can implement your own provider:

```
interface BillingProvider {
    public function resolvePlanFor(mixed $billable): ?Plan;
    public function pricesFor(Plan $plan): array;
}
```

---

Billable Objects
----------------

[](#billable-objects)

A billable can be:

- An Eloquent model
- Any object with an `id` property

```
$billable = new class {
    public int $id = 1;
};
```

---

Reset Periods
-------------

[](#reset-periods)

Each feature can define how often its usage resets:

```
ResetPeriod::NONE     // lifetime
ResetPeriod::DAILY
ResetPeriod::WEEKLY
ResetPeriod::MONTHLY
ResetPeriod::YEARLY
```

Usage is automatically grouped per period in the database.

---

Catalog (Pricing UI)
--------------------

[](#catalog-pricing-ui)

FeatureLimiter can generate ready-to-render structures for your pricing pages.

### Pricing cards (featured features)

[](#pricing-cards-featured-features)

```
$cards = FeatureLimiter::catalog()->plansCards(
    featured: ['sites', 'pages', 'storage', 'custom_code'],
    onlyActivePlans: true
);
```

### Full comparaison table (grouped)

[](#full-comparaison-table-grouped)

```
$table = FeatureLimiter::catalog()->comparisonTable(
    onlyActivePlans: true,
    onlyActiveFeatures: true
);
```

The returned structure contains plan headers + grouped feature rows (by feature.group), perfect for building a pricing comparison table.

Pricing (optional)
------------------

[](#pricing-optional)

FeatureLimiter can optionally fetch and expose plan prices through a billing provider (e.g. Stripe via Cashier).

### 1) Catalog output (cards / comparison table)

[](#1-catalog-output-cards--comparison-table)

By default, catalogs do **not** include prices (no external provider calls):

```
FeatureLimiter::catalog()->plansCards(['site', 'page', 'storage', 'banner', 'cms', 'collection']);
FeatureLimiter::catalog()->comparisonTable();
```

If you want prices, explicitly enable them:

```
FeatureLimiter::catalog()
    ->includePrices()
    ->plansCards(['site', 'page', 'storage', 'banner', 'cms', 'collection']);

FeatureLimiter::catalog()
    ->includePrices()
    ->comparisonTable();
```

### 2) Single plan

[](#2-single-plan)

By default, viewPlan() returns the plan reader without prices:

```
FeatureLimiter::viewPlan('free');
```

To fetch prices for a specific plan:

```
FeatureLimiter::viewPlan('free')->prices();
```

### 3) Billable plan

[](#3-billable-plan)

By default, resolving the billable plan does not fetch prices:

```
FeatureLimiter::for($billable)->plan();
```

To fetch prices for the resolved plan:

```
FeatureLimiter::for($billable)->plan()->prices();
```

---

Pruning Old Feature Usages
--------------------------

[](#pruning-old-feature-usages)

Over time, the `fl_feature_usages` table can grow significantly. FeatureLimiter provides a built-in Artisan command to clean up old usage records while keeping recent data for analytics, reporting, and charts.

### Basic usage

[](#basic-usage)

Remove all usage records older than **12 months**:

```
php artisan feature-limiter:prune-usages --months=12
```

Remove all usage records older than **90 days** (dry run – no deletion):

```
php artisan feature-limiter:prune-usages --days=90 --dry-run
```

### Optional flags

[](#optional-flags)

OptionDescription`--days=90`Keep only the last 90 days of usage`--months=12`Keep only the last 12 months of usage`--years=2`Keep only the last 2 years of usage`--dry-run`Show what would be deleted without deleting`--prune-zero`Also remove rows where `used = 0`### Scheduler example

[](#scheduler-example)

```
$schedule->command('feature-limiter:prune-usages --months=12')->daily();
```

---

Why keep historical usages?
---------------------------

[](#why-keep-historical-usages)

FeatureLimiter stores all usage records by default so you can:

- Build usage charts
- Generate reports
- Track growth over time
- Audit consumption behavior

The pruning command gives you **full control** over how much history you keep.

---

License
-------

[](#license)

MIT

Contributing
------------

[](#contributing)

See `CONTRIBUTING.md`.

###  Health Score

41

—

FairBetter than 89% of packages

Maintenance82

Actively maintained with recent releases

Popularity14

Limited adoption so far

Community6

Small or concentrated contributor base

Maturity51

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 100% 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 ~5 days

Total

7

Last Release

96d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/a8f96a2793e8b93821850a5ea9dc88fb80101cc0995820a986e7b55ff0ffe74c?d=identicon)[skaro42](/maintainers/skaro42)

---

Top Contributors

[![michael-lurquin](https://avatars.githubusercontent.com/u/7093602?v=4)](https://github.com/michael-lurquin "michael-lurquin (28 commits)")

---

Tags

billingfeatureslaravellaravel-packagelimitsphpplansquotasaasstripesubscriptionsusagelaravelbillingfeaturelimitersubscriptionsplansaasusagefeature-flagsquotalimits

### Embed Badge

![Health badge](/badges/michael-lurquin-feature-limiter/health.svg)

```
[![Health](https://phpackages.com/badges/michael-lurquin-feature-limiter/health.svg)](https://phpackages.com/packages/michael-lurquin-feature-limiter)
```

###  Alternatives

[mollie/laravel-cashier-mollie

Laravel Cashier provides an expressive, fluent interface to Mollie's subscription billing services.

172155.4k1](/packages/mollie-laravel-cashier-mollie)[chargebee/cashier

Laravel Cashier for Chargebee provides an expressive, fluent interface to Chargebee's subscription billing services.

627.4k1](/packages/chargebee-cashier)[asciisd/knet

Knet package is provides an expressive, fluent interface to KNet's payment services.

141.1k](/packages/asciisd-knet)

PHPackages © 2026

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