Subscription Kit
CommunityLaravel subscriptions with a ready-to-use Filament UI. Replace days of custom billing logic with a working setup on top of Stripe (Cashier). - Skip custom subscription logic - Skip building pricing UI - Skip wiring Stripe webhooks manually
Author:
Ihor K.
Package health
BetaAutomated checks of this plugin's Composer package
15 checks
- Failed: GitHub Actions pinned to SHA — View details on Plumb
- Skipped: GitLab CI includes pinned to SHA
- Passed: Open security advisories
- Warning: Dependabot PR responsiveness — Stale Dependabot PRs: oldest general PR is 95 days old.
- Skipped: Renovate MR responsiveness
- Passed: Dependabot or Renovate configured
-
Failed:
Dependency update cooldown configured
—
No cooldown configured in
.github/dependabot.yml. View details on Plumb - Failed: Provides a security policy — View details on Plumb
- Passed: Abandoned or archived — No consulted source marks the package abandoned (packagist, github).
- Passed: Commit and release recency — Active: last commit 36 days ago; last release 36 days ago.
-
Passed:
composer.lock not committed by library
—
composer.lockis absent from the released dist archive. - Passed: Dist archive is lean
-
Passed:
Current Laravel version supported
—
Package dependencies resolve together with current Laravel
13.0. -
Passed:
Current PHP version supported
—
Constraint
^8.4supports current PHP8.5. - Skipped: Current Symfony version supported
filament/
namespace. Review the source and install at your own risk. Found
malware or an unresolved security issue the author won't
address?
Report it
.
Documentation
- What it does
- Live Demo
- What it does NOT do
- Requirements
- Installation
- Configuration
- Stripe setup
- Usage
- Plan Sets
- Plan Features
- Plan Limits
- B2B usage
- Themes
- REST API
- Subscription states
- Listening to subscription events
- Publish tags
- Running tests
- License
Laravel subscriptions with a ready-to-use Filament UI.
Replace days of custom billing logic with a working setup on top of Stripe (Cashier).
- Skip custom subscription logic
- Skip building pricing UI
- Skip wiring Stripe webhooks manually
SubKit provides a Filament admin panel to manage plans and plan sets, themeable Blade components for your pricing page and subscription dashboard, and a clean PHP API for subscription lifecycle operations.
Why use SubKit? While Laravel Cashier is incredibly powerful, building the actual UI and admin panel for subscriptions takes days. SubKit bridges this gap by providing a "Lickable UI" out of the box and a powerful Filament admin to manage it all without writing boilerplate code.
#What it does
- Integrates with Stripe via Laravel Cashier (webhooks, checkout sessions, billing portal)
- Provides a Filament admin panel to manage Plans, Plan Sets, Provider Prices, Features, and Limits
- Provides Blade components: a pricing table and a subscription management UI, with multiple themes
- Exposes a PHP facade and REST API for subscription operations (checkout, cancel, resume, billing portal)
- Exposes runtime plan limits (e.g.
max_locations,max_users) via aHasCapabilitiestrait with built-in caching
#Live Demo
Try it in action: subkit.noxls.net
- Register a test account to explore the customer billing flow.
- Once registered, navigate to
/adminto check out the Filament control panel.
#What it does NOT do
- Process payments or store card data
- Replace Stripe or Laravel Cashier — it orchestrates on top of them
- Handle invoicing, taxes, or compliance
- Manage user authentication or access control
#Requirements
- PHP 8.4+
- Laravel 11+
- Laravel Cashier (
laravel/cashier^16.5) installed and configured - Filament (
filament/filament^3.2) - MySQL 8+ (or MariaDB 10.5+)
- A Stripe account
#Installation
composer require karpovigorok/subkit
Publish the config and run migrations:
php artisan vendor:publish --tag=subkit-config
php artisan vendor:publish --tag=subkit-migrations
php artisan migrate
Register the plugin in your Filament panel provider:
use SubKit\Filament\SubKitPlugin;
public function panel(Panel $panel): Panel
{
return $panel
// ...
->plugin(SubKitPlugin::make());
}
#Configuration
php artisan vendor:publish --tag=subkit-config
Add to your .env:
STRIPE_KEY=pk_live_...
STRIPE_SECRET=sk_live_...
STRIPE_WEBHOOK_SECRET=whsec_...
# Optional
EASY_SUB_CURRENCY_CODE=USD
EASY_SUB_CURRENCY_SYMBOL=$
Your User model must use Cashier's Billable trait:
use Laravel\Cashier\Billable;
class User extends Authenticatable
{
use Billable;
}
To enable runtime plan limits and automatic cache flushing on subscription changes, also set billable_model in config/subkit.php:
'billable_model' => App\Models\User::class,
For team-level subscriptions:
'billable_model' => App\Models\Team::class,
#Stripe setup
#1. Create plans in the admin panel
Navigate to your Filament admin panel (usually /admin) → Plans → Create a plan. After creating a plan:
- Add a Stripe Price ID in the Provider Prices tab.
- Attach marketing features in the Features tab (shown in pricing tables).
- Define technical limits in the Limits tab (readable at runtime in your app logic).
#2. Register the Stripe webhook
Point your Stripe webhook to Cashier's built-in route:
https://your-app.com/stripe/webhook
Events to enable in Stripe:
customer.subscription.createdcustomer.subscription.updatedcustomer.subscription.deletedinvoice.paidinvoice.payment_failed
#Usage
#Pricing table
Drop the pricing table into any Blade view. The component uses the currently authenticated user automatically — no user ID needed.
<x-subkit::pricing-table
provider="stripe"
success-url="{{ route('dashboard') }}"
cancel-url="{{ route('pricing') }}"
/>
With a plan set (for multiple landing pages or A/B testing):
<x-subkit::pricing-table
set="homepage_2024"
provider="stripe"
success-url="{{ route('dashboard') }}"
cancel-url="{{ route('pricing') }}"
/>
#All pricing table props
| Prop | Type | Default | Description |
|---|---|---|---|
set |
string|null |
null |
Plan set code. If omitted, shows all active plans. |
theme |
string|null |
'default' |
UI theme (default, dark, light, or a custom theme). |
provider |
string |
'stripe' |
Payment provider. |
success-url |
string |
'' |
Redirect after successful checkout. Accepts a route name, relative path, or full URL. |
cancel-url |
string |
'' |
Redirect when the user cancels checkout. |
free-url |
string |
'' |
CTA destination for $0 plans (authenticated users). |
guest-redirect-url |
string|null |
null |
Where unauthenticated visitors are sent. Defaults to /register. |
company-id |
string|null |
null |
For B2B: attaches the subscription to a company rather than a user. |
subscribe-label |
string|null |
null |
Override the "Get Started" button text. |
free-label |
string|null |
null |
Override the "Get Started Free" button text. |
guest-label |
string|null |
null |
Override the "Create Account to Subscribe" button text. |
URL props accept a route name (e.g. 'dashboard'), a relative path (e.g. '/thanks?utm_source=fb'), or a full URL. Route names are resolved automatically. URLs set in the admin panel (per Plan Set) serve as defaults when the prop is omitted.
For the best UX, point Free Plan URL to a route that automatically creates a $0 subscription for the authenticated user.
Button labels follow a three-tier fallback: Blade prop → Plan Set admin setting → translation string.
#Manage subscriptions
Drop the manage component into your dashboard or account page:
<x-subkit::manage-subscriptions
return-url="{{ route('dashboard') }}"
/>
This renders the user's active subscriptions: plan name, status badge, trial/renewal dates, and action buttons (Cancel, Resume, Manage Billing). Renders nothing if the user has no subscriptions.
#All manage-subscriptions props
| Prop | Type | Default | Description |
|---|---|---|---|
theme |
string|null |
'default' |
UI theme. |
return-url |
string |
'' |
URL to return to from the Stripe billing portal. |
guest-redirect-url |
string|null |
null |
Where guests are sent. Renders a redirect link instead of the subscription UI. |
#Check subscription access
use SubKit\Facades\SubKit;
if (SubKit::hasAccess((string) auth()->id())) {
// User has an active or trialing subscription
}
hasAccess() returns true for active and trialing states.
#Get subscriptions for a user
$subscriptions = SubKit::forUser((string) auth()->id());
$active = SubKit::activeForUser((string) auth()->id()); // returns Cashier Subscription or null
#Cancel a subscription
// Cancel at period end (access continues until the billing period ends)
SubKit::cancel($subscriptionId);
// Cancel immediately
SubKit::cancel($subscriptionId, immediately: true);
#Resume a subscription
SubKit::resume($subscriptionId);
#Billing portal
Redirect the user to the Stripe-hosted billing portal to manage payment methods and invoices:
$url = SubKit::billingPortal($subscriptionId, route('dashboard'));
return redirect()->away($url);
#Plan Sets
Plan Sets let you curate groups of plans for specific contexts — landing pages, A/B tests, regional pricing, etc.
Create a plan set in the admin panel under Plan Sets, assign plans to it, and reference it by code:
<x-subkit::pricing-table set="startup_annual" />
Per Plan Set you can configure:
- Theme — override the default UI theme
- Description — subtitle shown above the pricing table
- URLs — default success, cancel, free, and guest redirect URLs
- Button Labels — override button text per set
#Plan Features
SubKit includes a normalized, many-to-many feature management system for marketing and UI display. Instead of hardcoding features in your Blade files, manage a global feature library (e.g., "Priority Support", "Unlimited Projects") in the Filament admin panel under Features.
Attach features to plans via the Features tab on the plan edit page. SubKit's pricing tables automatically render them with checkmarks inside the pricing cards.
Note: Features are for presentation only — they have no effect on application logic. Use Plan Limits for enforcing technical constraints in your code.
#Plan Limits
Plan Limits are backend-only key-value constraints per plan (e.g. max_locations = 100, max_maps = 5). They are intentionally separate from Features: limits drive engine logic, features drive marketing copy.
#Defining limits
In the Filament admin panel, open a plan and go to the Limits tab. Each limit has:
| Field | Description |
|---|---|
| Key | Snake-case identifier used in code — e.g. max_locations |
| Value | The raw value stored as a string |
| Type | How the value is cast when read: Integer, Boolean, or String |
#Reading limits on a plan
$plan->getLimit('max_locations'); // 100 (int)
$plan->getLimit('can_export'); // true (bool)
$plan->getLimit('tier'); // 'gold' (string)
$plan->getLimit('nonexistent', 0); // 0 — custom default
#Reading limits at runtime via HasCapabilities
Add the HasCapabilities trait to your billable model alongside Cashier's Billable:
use Laravel\Cashier\Billable;
use SubKit\Concerns\HasCapabilities;
class User extends Authenticatable
{
use Billable, HasCapabilities;
}
Then call getCapabilities() anywhere in your application:
$capabilities = $user->getCapabilities();
// ['limits' => ['max_locations' => 100, 'max_maps' => 5]]
$max = $capabilities['limits']['max_locations'] ?? 0;
if ($user->locations()->count() >= $max) {
abort(403, 'Location limit reached for your plan.');
}
getCapabilities() resolves the user's active subscription → matches it to a SubKit plan → returns all limits with their types cast. Results are cached for 5 minutes.
If the user has no active subscription, getCapabilities() returns ['limits' => []] without throwing.
#Flushing the cache manually
$user->flushCapabilitiesCache();
#Automatic cache flush on subscription change
When billable_model is set in config/subkit.php, SubKit listens to Stripe's customer.subscription.created/updated/deleted webhooks and automatically calls flushCapabilitiesCache() on the affected model. No extra setup needed.
#B2B usage
For company-level subscriptions, pass company-id:
<x-subkit::pricing-table
:company-id="(string) $company->id"
provider="stripe"
success-url="{{ route('dashboard') }}"
cancel-url="{{ route('pricing') }}"
/>
company-id is a plain string with no foreign key constraint — it can reference any table in your app (teams, organizations, workspaces, etc.).
#Themes
Three themes are bundled: default, dark, and light. Specify the theme per component or per Plan Set.
To create a custom theme, publish the views and add a new folder:
php artisan vendor:publish --tag=subkit-views
Create resources/views/vendor/subkit/themes/{your-theme}/pricing-table.blade.php. The theme will appear automatically in the admin panel's theme selector.
#REST API
| Method | URL | Description |
|---|---|---|
POST |
/api/subkit/checkout |
Create a Stripe Checkout session |
GET |
/api/subkit/subscriptions/user |
List subscriptions for the authenticated user |
GET |
/api/subkit/subscriptions/company |
List subscriptions for a company |
POST |
/api/subkit/subscriptions/{id}/cancel |
Cancel a subscription |
POST |
/api/subkit/subscriptions/{id}/resume |
Resume a subscription |
Add authentication middleware in config/subkit.php:
'api' => [
'middleware' => ['api', 'auth:sanctum'],
'prefix' => 'api/subkit',
],
#Subscription states
Subscription state is owned by Cashier and sourced from Stripe's stripe_status field:
| State | Meaning |
|---|---|
trialing |
In a free trial period |
active |
Paid and active |
past_due |
Payment failed, awaiting retry |
paused |
Paused by the customer |
canceled |
Canceled (may still have access until period end) |
incomplete |
Checkout started but not completed |
#Listening to subscription events
SubKit delegates all webhook processing to Cashier. To react to lifecycle changes, listen to Cashier's WebhookHandled event in your AppServiceProvider:
use Laravel\Cashier\Events\WebhookHandled;
Event::listen(WebhookHandled::class, function (WebhookHandled $event) {
if ($event->payload['type'] === 'customer.subscription.deleted') {
// revoke access, send email, etc.
}
});
#Publish tags
| Tag | Publishes |
|---|---|
subkit-config |
config/subkit.php — includes billable_model setting |
subkit-migrations |
All package migrations (plans, features, limits, etc.) |
subkit-views |
Blade views (for customization) |
subkit-lang |
Translation strings |
#Running tests
composer test
#License
MIT
Featured Plugins
A selection of plugins curated by the Filament team
Blueprint
Filament Blueprint is a premium Laravel Boost extension that helps AI agents produce accurate, detailed implementation plans and security reports for Filament apps.
Filament
Advanced Tables (formerly Filter Sets)
Supercharge your tables with powerful features like user-customizable views, quick filters, multi-column sorting, advanced table searching, convenient view management, and more. Compatible with Resource Panel Tables, Relation Managers, Table Widgets, and Table Builder!
Kenneth Sese
Custom Dashboards
Let your users build and share their own dashboards with a drag-and-drop interface. Define your data sources in PHP and let them do the rest.
Filament
