Laravel Packages

Tashil: Laravel Subscription Plans and Feature Usage Limits

Published
Tashil: Laravel Subscription Plans and Feature Usage Limits image

Some Laravel apps sell plans that differ by what the customer can do as well as by price. A podcast hosting app, for example, might sell a Hobby plan with four episode uploads a month and a Studio plan with 30 uploads and a custom domain. Tashil, a package by Foysal Ahmed, stores those plans in your database and tracks how much of each feature a customer has used.

Tashil doesn't take payments. You charge customers through Stripe, Paddle, or another gateway, then record the payment in Tashil.

Main Features

  • On/off features, usage limits, and metered pricing for each plan
  • Usage limits that reset on a schedule, such as monthly
  • Trials, cancellation with a grace period, upgrades, and scheduled downgrades
  • Invoices and a record of each payment and refund
  • Scheduled commands that create renewal invoices and end expired trials
  • Route middleware and Blade directives for checking a plan or feature
  • MRR and churn reports

Creating Plans

A subscriber can be any Eloquent model. In the podcast app, teams subscribe, so the Team model gets the Subscribable contract and the HasSubscriptions trait:

class Team extends Model implements Subscribable
{
use HasSubscriptions;
}

Then you create features and attach them to a plan with a value:

$uploads = Tashil::feature('episode-uploads')->name('Episode Uploads')->limit()->create();
$uploads->update(['reset_period' => ResetPeriod::Monthly]);
 
$customDomain = Tashil::feature('custom-domain')->name('Custom Domain')->boolean()->create();
 
$hobby = Tashil::package('hobby')
->name('Hobby')
->price(12)
->monthly()
->feature($uploads, value: '4')
->feature($customDomain, value: 'false')
->create();

If you change a plan later, teams already on it keep the limits they signed up with. Only new subscribers get the new values.

Tracking Usage

useFeature() adds one to the count. If the team is already at its limit, it returns false instead. On the Hobby plan, the fifth upload in a month is rejected:

if (! $team->useFeature('episode-uploads')) {
return back()->withErrors([
'audio' => "You've used all {$team->featureValue('episode-uploads')} uploads this month.",
]);
}

The limit check and the update happen in one database query, so two uploads at the same moment can't both go over the limit.

When a team reaches 80% of a limit, Tashil fires a UsageLimitWarning event once for that period. On a Studio plan with 30 uploads, that happens on the 24th upload. A listener could use it to email the team about upgrading.

The Blade directives and route middleware check the logged-in user by default. Teams hold the subscriptions in this app, so a service provider tells Tashil how to find the current team:

Tashil::resolveSubscribableUsing(fn () => auth()->user()?->currentTeam);

A view can then show the custom domain settings only to teams whose plan includes them:

@feature('custom-domain')
<x-domain-settings :show="$show" />
@else
<p>Custom domains are available on the Studio plan.</p>
@endfeature

Subscribing and Recording Payments

Subscribing to a paid plan without a trial creates an invoice. The subscription becomes active when you record the payment from your gateway:

$subscription = $team->subscribe($hobby);
 
$invoice = Tashil::billing()->pendingInvoice($subscription);
 
Tashil::billing()->recordPayment($invoice, gateway: 'paddle', transactionId: 'txn_01j9k2');
 
$team->subscribed(); // true

If a webhook arrives twice and records the same payment again, Tashil returns the existing record instead of creating a duplicate.

A plan with a trial starts the trial right away, without an invoice:

$team->subscribe($studio, withTrial: true);

Upgrades, downgrades, and cancellations each have a method:

$team->switchPlan($studio); // upgrade right away
$team->scheduleDowngrade($hobby); // downgrade at the end of the billing period
$team->cancelSubscription(); // access continues until the period ends

Tashil's scheduled commands create renewal invoices, reset usage counts, end expired trials, and apply downgrades when a billing period ends.

Revenue Reports

Tashil::analytics() calculates figures from the subscriptions and invoices Tashil stores:

Tashil::analytics()->calculateMRR(); // monthly recurring revenue
Tashil::analytics()->churnRate(days: 30); // % of subscriptions cancelled in the last 30 days
Tashil::analytics()->trialConversionRate(); // % of trials that became paid

The MRR figure includes teams that are still on a trial.

Alternatives

  • Subscriptionify is the closest match. It handles plans, feature limits, and usage without depending on a payment gateway. It doesn't create invoices or revenue reports, but it can give a single customer extra quota
  • Laravel Cashier and Laravel Polar each work with one payment provider, and you set up your plans in that provider
  • Laravel Pennant turns features on and off for users, without plans or usage limits

Installation

Tashil requires PHP 8.2 or later and supports Laravel 10 through 13.

composer require foysal50x/tashil
php artisan vendor:publish --tag=tashil-config
php artisan vendor:publish --tag=tashil-migrations
php artisan migrate

Learn more about Tashil and view the source code on GitHub.

Yannick Lyn Fatt photo

Staff Writer at Laravel News and Full stack web developer.

Sponsored

masteringlaravel logo
Laravel Code Review

Get expert guidance in a few days with a Laravel code review

Visit Laravel Code Review

The latest

View all →
Decide with Jev: Laravel AI That Answers with a Probability image

Decide with Jev: Laravel AI That Answers with a Probability

Read article
JetBrains Air: Run AI Coding Agents in JetBrains IDEs image

JetBrains Air: Run AI Coding Agents in JetBrains IDEs

Read article
Memoize Tagged Cache Reads in Laravel image

Memoize Tagged Cache Reads in Laravel

Read article
Eloquent Refreshes: Load Generated Columns After Save image

Eloquent Refreshes: Load Generated Columns After Save

Read article
Laravel AI SDK 1.0 Adds Classification and Tool Approvals image

Laravel AI SDK 1.0 Adds Classification and Tool Approvals

Read article
Tagged Memoized Cache and Model Refreshes in Laravel 13.33 image

Tagged Memoized Cache and Model Refreshes in Laravel 13.33

Read article