Billing Console plugin screenshot
Dark mode ready
No multilingual support
Supports v5.x

Billing Console by Muhammad Amjad

Community

A free, read-only billing console for Laravel Cashier (Stripe) that shows your customers, subscriptions, invoices and payments, with an MRR and churn summary, "Open in Stripe" links and a safe "Sync from Stripe" action, inside your Filament panel.

Tags: Panels Widget
Supported versions:
5.x 4.x
Muhammad Amjad avatar Author: Muhammad Amjad

Package health

Automated checks of this plugin's Composer package

92 / 100
Security 85
Maintenance 100
Ecosystem 100
15 checks
  • Skipped: GitHub Actions pinned to SHA
  • Skipped: GitLab CI includes pinned to SHA
  • Passed: Open security advisories
  • Passed: Dependabot PR responsiveness — No open Dependabot PRs.
  • Skipped: Renovate MR responsiveness
  • Skipped: Dependabot or Renovate configured
  • Skipped: Dependency update cooldown configured
  • 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 0 days ago; last release 0 days ago.
  • Passed: composer.lock not committed by library — composer.lock is 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.2 supports current PHP 8.5.
  • Skipped: Current Symfony version supported
Third-party plugin. This is built by the community, not the Filament team. Filament does not review, endorse, or vet the security of plugins outside the 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 .
Powered by Plumb Last scanned 4 days ago

Documentation

Billing Console: customers, subscriptions and revenue in Filament

A free, read-only billing admin for apps that bill with Laravel Cashier (Stripe). See your customers, subscriptions, invoices and payments, plus MRR and churn, in your Filament panel instead of switching to the Stripe dashboard. It works with Filament 4 and 5.

Latest Version PHP Version License

The subscriptions page with the MRR summary in a Filament panel

The subscriptions page in dark mode

#What it does

Cashier keeps your subscriptions in your own database and everything else in Stripe. This plugin puts both in one place, under a Billing group in your panel:

  • Customers: every user with a Stripe customer ID, their payment method and number of current subscriptions (active, trialing or past due). Search by name, email or Stripe ID; filter to who has a current subscription. The customer page shows their subscriptions, invoices and payments.
  • Subscriptions: from your Cashier tables, with status (active, trialing, past due, cancelled, ...), price, trial and end dates. Filter by status or by "cancelled, still in grace period". Past-due and unpaid subscriptions are counted next to the menu item.
  • Invoices and Payments: the latest invoices and charges, read from the Stripe API, with links to the invoice PDF and the receipt. Filter by status (for payments, the filter works on the loaded payments); failed payments show Stripe's reason.
  • MRR and churn: monthly recurring revenue at list price, active, past-due and trialing subscriptions, and churn over the last 30 days. On the dashboard and above the subscriptions table.
  • Open in Stripe: every customer, subscription, invoice and payment links to the same record in the Stripe dashboard, in test or live mode to match your key.
  • Sync from Stripe: on a subscription, reads Stripe's copy and stores its status, dates and items in your Cashier tables with Cashier's own webhook code (customer.subscription.updated). Handy when a webhook was missed. Two differences from the webhook, on purpose: an incomplete_expired subscription keeps its row (only the status is updated; the webhook deletes it), and a sync is refused when Stripe says the subscription belongs to a different customer than its owner here. On a customer, it refreshes the payment method and syncs the subscriptions already in your Cashier tables; subscriptions that exist only in Stripe are not imported.

It never changes anything in Stripe. There are no refund, cancel, swap or charge buttons; those stay in the Stripe dashboard (or in a future Pro version, see below). "Sync from Stripe" only writes to your own Cashier tables, and it only runs after you confirm it.

#Requirements

  • PHP 8.2+ (8.3+ for Laravel 13)
  • Laravel 12 or 13
  • Filament 4 or 5
  • laravel/cashier 16 with its migrations run, and a Stripe secret key in STRIPE_SECRET

#Installation

composer require amjadiqbal/filament-billing-console

Add the plugin to your panel:

use AmjadIqbal\FilamentBillingConsole\BillingConsolePlugin;

public function panel(Panel $panel): Panel
{
    return $panel
        // ...
        ->plugin(BillingConsolePlugin::make());
}

That's it. The plugin reads the same configuration Cashier uses: Cashier::$customerModel (usually App\Models\User), Cashier::$subscriptionModel, cashier.secret and cashier.currency. It has no migrations, config file or assets of its own, and it renders with Filament's own components, so it looks right in a stock panel.

#Which Stripe key

Any secret key Cashier can use works. For the console alone, a restricted key with read access is enough: Customers, Subscriptions, Invoices, Charges, Prices and Payment Methods (read). Your app's checkout still needs the key it already has; the console uses whatever is in cashier.secret. The key is never shown in the panel, and error messages from Stripe are cleaned of anything that looks like a key.

#Who can see it

Billing data is sensitive, so decide who sees it. In order, the console uses:

  1. authorizeUsing() on the plugin, if you set it. It replaces everything below.

    BillingConsolePlugin::make()
        ->authorizeUsing(fn (string $ability, $record, $user): bool => $user?->is_admin === true);
    

    $ability is one of customers.viewAny, customers.view, customers.sync, subscriptions.viewAny, subscriptions.view, subscriptions.sync, invoices.viewAny, payments.viewAny, overview.view.

  2. A viewBillingConsole gate, if your app defines one. Every ability must pass it first:

    Gate::define('viewBillingConsole', fn (User $user): bool => $user->is_admin);
    
  3. Your model policies: the customer model's policy for customers, the subscription model's policy for subscriptions (viewAny, view; "sync" uses a sync method if there is one, then update, then view).

  4. Otherwise everyone who can open the panel can see it. Panel access itself is already guarded by canAccessPanel().

A policy that exists but has no method for the checked ability (for example no view) allows it, as when there is no policy. The resources are not scoped to a Filament tenant: in a multi-tenant panel, limit them with authorizeUsing() or a policy.

The resources are read-only: create, edit and delete are always denied.

#Options

BillingConsolePlugin::make()
    ->navigationGroup('Billing')          // default "Billing"
    ->navigationSort(10)
    ->customers(true)                     // turn any part off with false
    ->subscriptions(true)
    ->invoices(true)
    ->payments(true)
    ->dashboardWidget(true)               // MRR and churn on the panel dashboard
    ->customerAttributes('name', 'email') // your customer model's name and email columns (email may be null)
    ->currency('usd')                     // MRR currency, default config('cashier.currency')
    ->cacheFor(300)                       // seconds to cache Stripe API reads, 0 to turn the cache off
    ->maxListRecords(100)                 // latest invoices or payments loaded per table (Stripe's page maximum is 100)
    ->syncsPerMinute(10);                 // "Sync from Stripe" actions per user per minute

#How the numbers are worked out

  • MRR adds up the subscription items of active and past-due subscriptions in your Cashier tables, at the list price of their Stripe price (yearly divided by 12, weekly times 52/12, and so on, times quantity). Not counted: discounts and coupons, tax, metered and tiered prices (they have no fixed amount), and prices in another currency. The card says how many items it left out. Trials are not counted.
  • Churn, last 30 days is the number of subscriptions that are cancelled and ended in the last 30 days, divided by those plus today's active and past-due ones.
  • Counts come from your database, so they are as current as your webhooks. Prices come from Stripe and are cached; only the first 1,000 recurring prices are read, and items on any other price are "not counted".

These are quick operating numbers, not accounting figures. For revenue reporting, use Stripe's own reports.

#Stripe API use

The console only reads from Stripe. The tables and the MRR card list invoices, charges and recurring prices; these lists are cached (5 minutes by default, per mode), the client retries network errors twice, and failures (bad key, missing permission, rate limit, network) show a clear message in the table instead of an error page. Failures are not cached, so the next page load tries again.

"Sync from Stripe" reads live, never from the cache: the subscription, and through Cashier the customer with its default payment method and, for a subscription cancelled at period end, its first item. Every sync attempt, including a failed one, counts toward the per-user syncsPerMinute() limit.

Invoices and payments tables load the latest 100 records (your maxListRecords()), then search, sort and paginate those in the panel. Filtering invoices by status asks Stripe for that status, so older ones show up too.

#Testing

The test suite never calls Stripe. It serves recorded Stripe responses through stripe-php's HTTP client hook, and scripts/ci-local.sh also runs a contract test against Stripe's official stripe-mock (in Docker) to check the console against real response shapes.

composer test

#Pro (planned)

A paid Pro version is planned for the write operations this free version leaves out on purpose: refunds, cancel, resume and swap, coupons, a failed-payment queue, revenue charts, multiple Stripe accounts and more gateways (Paddle, Mollie). The free version stays free and read-only.

#Changelog

See CHANGELOG.md.

#Support

  • 🐛 Bug or feature request: open an issue
  • 🔒 Security issue: please report it privately through GitHub's security tab

#Need this customised, or something built?

I'm available for custom development, package integration, and technical consulting.

Hire me on Upwork

#More packages

This is part of a family of open-source packages. See all of them.

filament-uptime-monitor Uptime and SSL checks managed in Filament, for spatie/laravel-uptime-monitor
filament-deleted-records A recycle bin for Filament, for spatie/laravel-deleted-models
laravel-webhook-console Browse, inspect and replay received webhooks, for spatie/laravel-webhook-client

#Credits

This is an independent project. It is not affiliated with, endorsed by or sponsored by Stripe, Inc. or Laravel Holdings Inc. "Stripe" and "Laravel" are trademarks of their owners and are used here only to say what the plugin works with.

#License

The MIT License (MIT). See LICENSE.md.

The author

Muhammad Amjad avatar Author: Muhammad Amjad

I am a seasoned full-stack web developer specializing in Laravel, Livewire, and Vue.js ecosystems. With extensive experience in building modern web applications, I leverage the power of Laravel's elegant syntax and robust features to create scalable backend solutions. My expertise extends to crafting responsive and interactive user interfaces using Livewire and Vue.js, enhanced by the utility-first approach of Tailwind CSS.

Plugins
12
Stars
42

From the same author

Record Settings plugin thumbnail

Record Settings

Edit per-user, per-team and per-tenant settings in Filament from a settings page, a modal action or resource form fields, stored with glorand/laravel-model-settings or in a JSON column.

Muhammad Amjad avatar Author: Muhammad Amjad
92 / 100 package health score out of 100
2 stars
Recently added: New Tag: Panels More tags: +2
Dark mode ready No multilingual support
Free
Get it now
Uptime Monitor plugin thumbnail

Uptime Monitor

An admin UI for spatie/laravel-uptime-monitor that lets you add, edit, turn on and off and check uptime and SSL certificate monitors from your Filament panel, with status filters and a dashboard widget.

Muhammad Amjad avatar Author: Muhammad Amjad
92 / 100 package health score out of 100
1 star
Recently added: New Tag: Panels More tags: +2
Dark mode ready No multilingual support
Free
Get it now
Stat Cards plugin thumbnail

Stat Cards

Richer stat cards for the Filament stats overview widget, adding progress bars, icon tiles, background, label and value colours, trends, badges and bar charts to the widget you already have.

Muhammad Amjad avatar Author: Muhammad Amjad
92 / 100 package health score out of 100
1 star
Recently added: New Tag: Widget
Dark mode ready No multilingual support
Free
Get it now
XLSX Import plugin thumbnail

XLSX Import

Lets Filament's own ImportAction read Excel (.xlsx) and OpenDocument (.ods) workbooks as well as CSV, with a sheet picker, while your existing Importer classes, queued jobs and failed-rows downloads keep working unchanged.

Muhammad Amjad avatar Author: Muhammad Amjad
92 / 100 package health score out of 100
2 stars
Recently added: New Tag: Action More tags: +1
Dark mode ready No multilingual support
Free
Get it now