PHPackages                             karpovigorok/subkit - 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. karpovigorok/subkit

ActiveLibrary[Payment Processing](/categories/payments)

karpovigorok/subkit
===================

A Laravel package for subscription and billing orchestration.

v2.0.0(1mo ago)34325[2 PRs](https://github.com/karpovigorok/subkit/pulls)MITPHPPHP ^8.4CI passing

Since Apr 3Pushed 1mo ago1 watchersCompare

[ Source](https://github.com/karpovigorok/subkit)[ Packagist](https://packagist.org/packages/karpovigorok/subkit)[ Docs](https://github.com/karpovigorok/subkit)[ GitHub Sponsors]()[ RSS](/packages/karpovigorok-subkit/feed)WikiDiscussions main Synced 6d ago

READMEChangelog (1)Dependencies (28)Versions (7)Used By (0)

SubKit
======

[](#subkit)

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 Pricing](art/demo-how-subkit-works.gif)](art/screen-subkit-theme-default.png)

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
------------

[](#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 a `HasCapabilities` trait with built-in caching

Live Demo
---------

[](#live-demo)

Try it in action: **[subkit.noxls.net](https://subkit.noxls.net)**

- Register a test account to explore the customer billing flow.
- Once registered, navigate to **`/admin`** to check out the Filament control panel.

What it does NOT do
-------------------

[](#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
------------

[](#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
------------

[](#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
-------------

[](#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
------------

[](#stripe-setup)

### 1. Create plans in the admin panel

[](#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

[](#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.created`
- `customer.subscription.updated`
- `customer.subscription.deleted`
- `invoice.paid`
- `invoice.payment_failed`

---

Usage
-----

[](#usage)

### Pricing table

[](#pricing-table)

Drop the pricing table into any Blade view. The component uses the currently authenticated user automatically — no user ID needed.

```

```

With a plan set (for multiple landing pages or A/B testing):

```

```

#### All pricing table props

[](#all-pricing-table-props)

PropTypeDefaultDescription`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

[](#manage-subscriptions)

Drop the manage component into your dashboard or account page:

```

```

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

[](#all-manage-subscriptions-props)

PropTypeDefaultDescription`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

[](#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

[](#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-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

[](#resume-a-subscription)

```
SubKit::resume($subscriptionId);
```

### Billing portal

[](#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)

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:

```

```

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
-------------

[](#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](#plan-limits) for enforcing technical constraints in your code.

---

Plan Limits
-----------

[](#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

[](#defining-limits)

In the Filament admin panel, open a plan and go to the **Limits** tab. Each limit has:

FieldDescription**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

[](#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`

[](#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

[](#flushing-the-cache-manually)

```
$user->flushCapabilitiesCache();
```

#### Automatic cache flush on subscription change

[](#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
---------

[](#b2b-usage)

For company-level subscriptions, pass `company-id`:

```

```

`company-id` is a plain string with no foreign key constraint — it can reference any table in your app (`teams`, `organizations`, `workspaces`, etc.).

---

Themes
------

[](#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
--------

[](#rest-api)

MethodURLDescription`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 subscriptionAdd authentication middleware in `config/subkit.php`:

```
'api' => [
    'middleware' => ['api', 'auth:sanctum'],
    'prefix'     => 'api/subkit',
],
```

---

Subscription states
-------------------

[](#subscription-states)

Subscription state is owned by Cashier and sourced from Stripe's `stripe_status` field:

StateMeaning`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
--------------------------------

[](#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
------------

[](#publish-tags)

TagPublishes`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
-------------

[](#running-tests)

```
composer test
```

---

License
-------

[](#license)

MIT

###  Health Score

48

—

FairBetter than 94% of packages

Maintenance89

Actively maintained with recent releases

Popularity22

Limited adoption so far

Community13

Small or concentrated contributor base

Maturity56

Maturing project, gaining track record

 Bus Factor1

Top contributor holds 70% 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 ~78 days

Total

2

Last Release

56d ago

Major Versions

v1.0.0 → v2.0.02026-06-20

### Community

Maintainers

![](https://avatars.githubusercontent.com/u/7913548?v=4)[Ihor Karpov](/maintainers/karpovigorok)[@karpovigorok](https://github.com/karpovigorok)

---

Top Contributors

[![karpovigorok](https://avatars.githubusercontent.com/u/7913548?v=4)](https://github.com/karpovigorok "karpovigorok (7 commits)")[![dependabot[bot]](https://avatars.githubusercontent.com/in/29110?v=4)](https://github.com/dependabot[bot] "dependabot[bot] (2 commits)")[![github-actions[bot]](https://avatars.githubusercontent.com/in/15368?v=4)](https://github.com/github-actions[bot] "github-actions[bot] (1 commits)")

---

Tags

billingfilament-pluginfilamentphplaravellaravel-cashierlaravel-packagelaravel-saaspaymentspricingpricing-tablesaassaas-boilerplatestarter-kitstripesubscriptionstall-stacklaravelIhor Karpovsubkit

###  Code Quality

TestsPest

Static AnalysisPHPStan

Code StyleLaravel Pint

### Embed Badge

![Health badge](/badges/karpovigorok-subkit/health.svg)

```
[![Health](https://phpackages.com/badges/karpovigorok-subkit/health.svg)](https://phpackages.com/packages/karpovigorok-subkit)
```

###  Alternatives

[rawilk/profile-filament-plugin

Profile &amp; MFA starter kit for filament.

3915.5k](/packages/rawilk-profile-filament-plugin)[backstage/mails

View logged mails and events in a beautiful Filament UI.

16429.7k](/packages/backstage-mails)[finity-labs/fin-mail

A powerful email template manager and composer for Filament with dynamic token replacement, template versioning, and inline email sending.

316.8k2](/packages/finity-labs-fin-mail)[stephenjude/filament-jetstream

A Laravel starter kit built with Filament inspired by Jetstream.

17861.9k3](/packages/stephenjude-filament-jetstream)[stephenjude/filament-debugger

About

104173.6k2](/packages/stephenjude-filament-debugger)[tapp/filament-maillog

Filament plugin to view outgoing mail

3076.0k1](/packages/tapp-filament-maillog)

PHPackages © 2026

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