tightenco/nova-stripe
Stripe metrics for Laravel Nova. Adds dashboard cards that pull monthly recurring revenue, new customers, churn, and subscription activity straight from the Stripe API, so you can monitor key billing stats inside Nova without leaving your admin panel.
tightenco/nova-stripe v3 is effectively a new package. It is no longer a pair of dashboard cards that call Stripe on render. It now syncs Stripe records into your database — Products, Customers, Charges, Subscriptions — and exposes them as Nova resources with search, filters, and pagination. If you used v2, read this section as a migration, not an upgrade.
Minimal install:
composer require tightenco/nova-stripe
php artisan vendor:publish --provider="Tighten\NovaStripe\NovaStripeServiceProvider"
php artisan migrate
Publish both the config and the migrations (publish without --tag on first install, then prune). Set keys via env — never hard-code:
STRIPE_KEY=pk_test_xxx
STRIPE_SECRET=sk_test_xxx
Register the resources in app/Nova/NovaServiceProvider::resources() — this is the shift from v2, where you registered cards in cards():
public function resources()
{
return [
new \Tighten\NovaStripe\Resources\Charge,
new \Tighten\NovaStripe\Resources\Customer,
new \Tighten\NovaStripe\Resources\Product,
new \Tighten\NovaStripe\Resources\Subscription,
];
}
Then populate the tables by running the package's sync command (check the service provider / src/Console for the exact signature in your installed tag) and, more importantly, put it on the scheduler.
Where to look first: src/ and the published database/migrations/. The README describes intent; the actual resource classes, table names, and config keys are what you'll be reading and overriding. Confirm the sync command name from the provider's commands() before assuming it.
First useful use case: a support-facing set of Nova resources for Customers and Subscriptions, synced hourly, so support can find a customer by email and see their subscription status and recent charges without touching the Stripe dashboard. That is the core value now — it just happens to require a scheduler and a database table to get there.
Treat the sync as ETL, not as a cache. The package gives you the fetch-and-store layer; the cadence is your decision. A pragmatic setup:
// app/Console/Kernel.php (or routes/console.php on Laravel 11)
$schedule->command('nova-stripe:sync')->hourly();
Hourly is fine for support browsing. If finance watches failed charges live, run it every five minutes and accept the API cost — but understand that "live" now means "as fresh as your last run", which is a genuinely different guarantee than v2.
Gate the resources, not just the fields. Authorization is still yours. Use Nova policies or canSee/canRun on the resource and its actions:
public static function authorizedToViewAny($request)
{
return $request->user()->hasRole('finance');
}
Customers and Subscriptions contain PII and revenue data; Charges contain amounts. Decide per-resource who sees what. A user who can reach the resource can browse every synced record.
Extend the resources rather than fork them. Subclass the package resource and add fields, metrics, or links:
namespace App\Nova;
use Tighten\NovaStripe\Resources\Customer as BaseCustomer;
class Customer extends BaseCustomer
{
public function fields(NovaRequest $request)
{
return array_merge(parent::fields($request), [
// your fields: account owner, internal org, MRR, etc.
]);
}
}
This is also the mechanism for scoping: override indexQuery to restrict to a region/brand/tenant. Prefer this over editing vendor files.
Join synced Stripe data to your own models. This is the biggest new capability and the reason to adopt v3. Store stripe_id on your users/teams table, then relate:
public function stripeCustomer()
{
return $this->belongsTo(\Tighten\NovaStripe\Models\Customer::class, 'stripe_id', 'stripe_id');
}
Now a Customer resource can show your internal account owner, and your own Nova resources can show subscription status. v2 could never do this because nothing was persisted.
Build the metrics v2 couldn't. With charges and subscriptions in SQL, "MRR this month", "failed charges last 7 days", and "refunds by product" become ordinary queries and ordinary Nova cards/metrics. Keep the package's resources for browsing and write your own trend cards against the synced tables — you now have history, so trends are possible.
Currency filtering is first-class. v3 adds currency filtering. If you settle in multiple currencies, lean on it rather than summing mixed-currency amounts. Do not let anyone add a total across currencies in a custom metric.
Staging vs production is now a data problem, not just a key problem. In v2, test keys produced fake cards that vanished with the env. In v3, test data is written into your database. Keep test and live syncs in separate databases, or at minimum separate tables via config, and never point a production database at test keys to "try it out" — you'll mix fake customers into real records.
Multi-account. The config is still a single key pair. For per-brand or per-region Stripe accounts, run a separate sync job per account (your own command wrapping StripeClient), tag rows with an account identifier, and scope resources with indexQuery. Hot-swapping the package config at runtime is not a viable pattern.
Balance, Charges), the card registration flow, and the config shape from v2 are gone or substantially changed. Any cards() entry referencing Tighten\NovaStripe\Cards\* will fail after upgrading. Plan the migration: publish new config/migrations, register resources, remove old cards, and delete the v2 config keys you no longer use.stripe_id. Confirm there's a unique index on it in the published migration; without one, repeated runs duplicate rows and your counts silently inflate. This is the first thing to check when numbers look wrong.stripe_id, customer/email, created_at, and currency — otherwise pagination degrades badly on large charge tables, which the package can't fix for you.env() reads inside config/nova-stripe.php only. env() in a resource or card returns null once config:cache runs, and the failure looks like an auth error.created_at values may be stored as-is or cast by the resource — check the field formatting in the resource before telling anyone the times are wrong.sk_test_/sk_live_ prefix, and consider a visible "TEST MODE" banner in the resource header.php artisan tinker and hit Stripe directly (\Stripe\Stripe::setApiKey(config('nova-stripe.secret')); \Stripe\Customer::all(['limit' => 1]);) to isolate keys/API access from Nova. Then verify the resource is registered in NovaServiceProvider::resources(). Then check the tables actually have rows — an empty resource is usually a sync that never ran or a scheduled command that failed silently. Run the sync manually with verbose output.->onFailure(...) or Laravel's scheduler failure events) so a broken sync surfaces before finance does.composer.json constraints against your Nova major (4/5) and PHP version before upgrading, and re-read src/ in the installed tag rather than relying on v2 docs, blog posts, or the current README.fields/indexQuery, publish and edit views if needed, and build your own cards against the synced tables. With a narrow scope and a fresh rewrite, upstream PRs are slower than a local subclass — don't fork.How can I help you explore Laravel packages today?