solution-forest/filament-panphp
Filament PanPHP integrates PanPHP analytics into Filament, letting you view page views, visitors, and traffic insights directly from your admin panel. Add analytics widgets and pages to monitor site activity without leaving Filament.
Prerequisites. A Laravel 10/11 app running Filament v3, plus the underlying analytics package panphp/pan (this plugin is a Filament UI layer — it does not collect data by itself). Install Pan first and get it recording page views before adding the plugin; debugging two things at once is the usual failure mode here.
composer require panphp/pan
php artisan vendor:publish --tag="pan-migrations"
php artisan migrate
Add Pan's tracking snippet to your public layout (not the admin panel layout, or you'll count your own dashboard visits):
{{-- resources/views/layouts/app.blade.php, before </body> --}}
@include('pan::tracking')
Install the plugin.
composer require solution-forest/filament-panphp
php artisan filament:assets
Register it on a panel in app/Providers/Filament/AdminPanelProvider.php:
use SolutionForest\FilamentPanphp\PanPlugin; // confirm FQCN in the README / src/
public function panel(Panel $panel): Panel
{
return $panel
->plugin(PanPlugin::make())
// ...
}
Where to look first, in this order:
vendor/solution-forest/filament-panphp/src/ — the plugin class and any Widgets/ or Pages/ directory. This tells you exactly what it registers and whether it's a page or a dashboard widget.vendor/solution-forest/filament-panphp/resources/views/ — what data it expects and how it queries it.config/pan.php and its pan table schema — retention, tracking options, and the columns the widget reads.First use case. Log into /admin, open the Pan page/widget, and confirm numbers match a manual SELECT COUNT(*) FROM pan for today. If they don't match, stop and fix data collection before wiring anything else — everything downstream depends on that table being right.
Internal-only analytics panel. The cleanest pattern with a package this small is a dedicated AnalyticsPanelProvider rather than bolting it onto your existing admin panel. Keeps the tracking surface area isolated and makes it trivial to restrict access:
->authGuard('web')
->domain('analytics.'.config('app.domain'))
Gate access explicitly. Never rely on the plugin's defaults for authorization. Use Filament's panel-level ->authMiddleware() / a policy, or if the plugin exposes a closure, wire it up:
PanPlugin::make()
->canAccess(fn () => auth()->user()?->hasRole('admin')),
If no such method exists in the version you install, subclass the widget/page and override canView() / canAccess(). Given the package's size, expect to do this.
Embed as a dashboard widget if you'd rather keep it alongside your other Filament widgets. Register the widget class explicitly and scope the grid column span:
->widgets([
\SolutionForest\FilamentPanphp\Widgets\PageViewsChart::class, // confirm class name
AccountWidget::class,
])
Multi-tenant / multi-site. Pan's default schema is single-tenant. If you need per-tenant analytics, either give tenants their own database connection and register the panel per-tenant, or add a tenant_id column to pan and fork the widget's query. Do not assume the plugin supports tenancy — verify first.
Retention and pruning. Raw page-view rows grow fast. Schedule pruning in routes/console.php or the kernel:
Schedule::command('model:prune', ['--model' => [\Pan\Models\Visit::class]])->daily();
Or a raw cleanup if Pan doesn't ship a pruneable model:
DB::table('pan')->where('created_at', '<', now()->subMonths(6))->delete();
Testing. Point the widget tests at seeded rows rather than real traffic:
it('shows today\'s page views', function () {
DB::table('pan')->insert([
['url' => '/pricing', 'created_at' => now()],
]);
Livewire::test(\SolutionForest\FilamentPanphp\Widgets\PageViewsChart::class)
->assertSuccessful();
});
Pair with your existing reporting. The widget is a read-only view over one table. If you already have a BI tool or a Filament chart you like, it's often faster to write a 20-line query against pan than to adapt this package.
Two packages, one version constraint. filament-panphp pins a Pan version. If Composer resolves an older Pan than the one whose migrations you already ran, you get silent column mismatches and blank widgets. Check composer show panphp/pan solution-forest/filament-panphp after install and align them deliberately.
Self-tracking contamination. The single most common complaint about privacy-friendly analytics: the snippet ends up in the admin layout via a shared Blade component, and your own dashboard refreshes inflate the numbers. Exclude by path or by user agent server-side, and verify by loading the admin panel with the network tab open.
Ad blockers and bots. Client-side tracking undercounts — typically 10–30% on developer-adjacent audiences. Server-side middleware tracking is more accurate but counts asset requests unless filtered. Pick one, document it, and don't compare absolute numbers against Google Analytics.
Asset and cache steps after every update. php artisan filament:assets, then php artisan optimize:clear. Filament caches component registration aggressively; a stale cache looks identical to a broken plugin.
Timezone mismatch. Pan stores UTC; Filament date filters and your app.timezone may not agree. "Today" can be off by a day for users outside UTC. Use ->timezone(config('app.timezone')) on the chart widget if available, otherwise group in SQL with the right offset.
Low maintenance signal — pin and vendor. 26 stars, no dependents, last release October 2024. Treat it as a starting point, not a dependency you can upgrade blindly. Pin an exact version ("solution-forest/filament-panphp": "1.0.*") and be ready to copy the widget into app/Filament/Widgets/ and own it. For a reporting widget this is usually a 100-line file — the fork cost is low.
Verify class names against src/ before copying examples. The public README for packages this size is often behind the code; the plugin class, widget namespace, and any config keys should be confirmed by reading the source. composer show --tree and grep -r "class " vendor/solution-forest/filament-panphp/src get you there in under a minute.
Extension points. The realistic ones are: Filament panel/plugin config, widget subclassing (override getData(), getOptions()), and the pan table itself. There is unlikely to be an event system or a documented service contract. If you need filtering by route group, campaign, or user cohort, plan to write the query yourself rather than hoping for a config flag.
Migration collisions. If you've previously installed other analytics or the pan migrations under a different timestamp, migrate can no-op or fail. php artisan migrate:status | grep pan before assuming the table exists in the shape the widget expects.
How can I help you explore Laravel packages today?