spatie/filament-simple-stats
Opinionated, prebuilt stat widgets for Filament dashboards. Quickly add daily counts and sums using Flowframe/laravel-trend, with helpers like last 30 days to generate clean, consistent stats cards with minimal setup.
Installation:
composer require spatie/filament-simple-stats
Ensure your project uses Filament v2+ and Laravel 10+ (or newer, as per changelog).
First Widget:
Create a Filament widget (e.g., app/Filament/Widgets/DashboardStats.php):
use Spatie\FilamentSimpleStats\SimpleStat;
use Filament\Widgets\Widget;
class DashboardStats extends Widget
{
protected static string $view = 'filament.widgets.dashboard-stats';
public function getStats(): array
{
return [
SimpleStat::make('Users')->model(\App\Models\User::class)->count(),
SimpleStat::make('Revenue')->model(\App\Models\Order::class)->sum('amount'),
];
}
}
Register Widget:
Add the widget to your Filament dashboard in app/Providers/Filament/AdminPanelProvider.php:
public function panel(Panel $panel): Panel
{
return $panel
->widgets([
\App\Filament\Widgets\DashboardStats::class,
]);
}
Run Migrations (if needed):
Ensure your models have the required columns (e.g., created_at for time-based queries).
Use SimpleStat to display counts, sums, averages, or minimums/maximums of Eloquent models with minimal boilerplate. Example:
SimpleStat::make('Active Users')
->model(\App\Models\User::class)
->where('active', true)
->last7Days()
->dailyCount(),
Model-Based Stats:
SimpleStat::make('Total Orders')
->model(\App\Models\Order::class)
->count(); // or sum('amount'), avg('price'), etc.
Time-Based Aggregations:
// Daily/weekly/monthly trends
SimpleStat::make('Daily Orders')
->model(\App\Models\Order::class)
->last30Days()
->dailySum('amount');
// Custom periods
SimpleStat::make('Monthly Revenue')
->model(\App\Models\Order::class)
->last12Months()
->monthlySum('amount');
Query Modifiers:
SimpleStat::make('User Orders')
->model(\App\Models\Order::class)
->where('user_id', auth()->id())
->where('status', 'completed')
->lastWeek()
->count();
Trend Customization:
// Disable trends (show raw values only)
SimpleStat::make('Errors')->model(\App\Models\Error::class)->count()->withoutTrend();
// Invert colors (e.g., for error rates)
SimpleStat::make('Errors')->model(\App\Models\Error::class)->count()->invertTrendColors();
Dynamic Data Sources: Use closures for dynamic queries (e.g., scoped to the current user or tenant):
SimpleStat::make('My Orders')
->query(fn () => \App\Models\Order::where('user_id', auth()->id()))
->count();
Custom Views:
Extend the default view (resources/views/filament/widgets/dashboard-stats.blade.php) to override styling or add tooltips:
<div class="filament-stats-card">
<x-filament::section>
<div class="flex items-center gap-4">
<div class="text-3xl font-bold">{{ $stat->value }}</div>
<div class="text-sm text-gray-500">{{ $stat->description }}</div>
</div>
</x-filament::section>
</div>
Caching:
Cache expensive queries in your widget’s update() method or use Laravel’s cache middleware:
public function update(): void
{
Cache::remember('dashboard_stats', now()->addHours(1), fn () => $this->getStats());
}
Localization: Translate stat labels and descriptions using Filament’s localization:
SimpleStat::make(__('filament::resources/users.title'))
->model(\App\Models\User::class)
->count();
Composite Stats: Combine multiple stats into a single widget for related metrics:
public function getStats(): array
{
return [
SimpleStat::make('Users')->model(\App\Models\User::class)->count(),
SimpleStat::make('Active Users')->model(\App\Models\User::class)->where('last_seen_at', '>', now()->subDay())->count(),
SimpleStat::make('Avg. Session')->model(\App\Models\User::class)->avg('session_duration'),
];
}
Conditional Stats: Show/hide stats based on user roles or permissions:
public function getStats(): array
{
$stats = [
SimpleStat::make('Admin Dashboard')->count(),
];
if (auth()->user()->isAdmin()) {
$stats[] = SimpleStat::make('Admin Users')->model(\App\Models\User::class)->where('role', 'admin')->count();
}
return $stats;
}
API-Driven Stats:
Fetch data from external APIs and format it for SimpleStat:
SimpleStat::make('API Calls')
->value(fn () => Http::get('https://api.example.com/metrics')->json()['count'])
->description('Last 24h');
Performance:
last30Days()) can be slow without proper database indexing.created_at (or your date column) is indexed:
Schema::table('orders', function (Blueprint $table) {
$table->index('created_at');
});
select() to limit columns:
SimpleStat::make('Orders')->model(\App\Models\Order::class)->select('id', 'amount')->sum('amount');
Trend Calculation:
last30Days()).endOfDay() to include today’s data:
SimpleStat::make('Daily Orders')->last30Days()->endOfDay()->dailySum('amount');
0 using a custom query:
SimpleStat::make('Orders')->query(fn () => \App\Models\Order::selectRaw('
COUNT(*) as count,
DATE(created_at) as date
)->groupBy('date')
->union([
\DB::raw("SELECT 0 as count, DATE(CURRENT_DATE) as date ON CONFLICT(date) DO NOTHING")
]))
->last30Days();
Model Ambiguity:
SimpleStat::make() may fail.query() method instead of model():
SimpleStat::make('Orders')->query(\App\Models\Order::query())->count();
Filament Version Mismatch:
composer.json or check the changelog for compatibility notes.Query Inspection: Use Laravel’s query logging to debug slow or incorrect queries:
\DB::enableQueryLog();
$stats = $this->getStats();
\Log::debug(\DB::getQueryLog());
Stat Value Overrides: Temporarily override stat values for testing:
SimpleStat::make('Test Stat')->value(42)->description('Hardcoded for testing');
Trend Debugging:
Check the raw trend data by inspecting the getTrend() method output:
$stat = SimpleStat::make('Orders')->last7Days()->dailySum('amount');
\Log::debug($stat->getTrend());
last7Days() by default for daily stats and last12Months() for monthly stats. Override explicitly if needed:
SimpleStat::make('Monthly Orders')->last12Month
How can I help you explore Laravel packages today?