amjadiqbal/laravel-first-party-analytics
First-party analytics for Laravel: collect and store visitor and event data on your own infrastructure without third-party scripts, giving you privacy-friendly, self-hosted reporting and insights.
Architecture fit. This is an in-process analytics collector: a middleware on the web group writes pageviews and visits into the host application's own database, with a Blade dashboard for reading them back. It fits the common case well — a Laravel 12/13 monolith that wants basic traffic, referrer, UTM, conversion and funnel reporting without shipping request data to Google or standing up a separate Plausible/Fathom/Umami service. It is explicitly not a product-analytics tool: no cohorts, retention curves, session replay, experiments, or user-level exploration mentioned. Scoping expectations accordingly is the single most important framing decision.
The fit weakens in three cases worth naming up front: multi-tenant SaaS (nothing in the notes indicates tenant scoping on the stored rows or the dashboard queries), API-only/headless backends (tracking is bound to successful HTML responses on web), and any deployment with full-page caching or a CDN serving cached HTML — the middleware never runs on a cache hit, so traffic would be silently undercounted by whatever fraction is served from cache.
Integration feasibility. Low friction: composer require plus php artisan migrate, and tracking is automatic via the web group. That "automatic" is also the main integration hazard — the package mutates a framework middleware group, so it must be verified against existing middleware ordering, tenancy resolution, and TrustProxies configuration. Everything else (Blade directive, facade, artisan commands, config-driven funnels) is conventional Laravel surface area.
Technical risk.
^0.1 narrowly, so minor bumps are effectively deliberate upgrade events. Budget for migration review on every bump, and pin exactly for the first months.Analytics::identify() and the Login/Registered user linking deliberately change the posture by joining behaviour to an identified person; that path deserves separate review and probably consent.TrustProxies is misconfigured or the app is reachable directly, clients can spoof it.Analytics::ignoreUsing(). Good defaults — but they create a permanent gap between this tool's numbers and any tool that ignores those signals, which will surface as "why don't the numbers match?"navigate-style navigation will not register pageviews. Prefetches skipped is correct; SPA-style transitions not counted is a real reporting limitation, not a bug.Key questions. Is there tenant scoping on stored rows and dashboard queries? Is a separate DB connection supported, and is the schema indexed for the dashboard's range queries? What is the intended path and timeline to 1.0? Who is expected to define the dashboard gate in production (local-only default is a safe default, not a policy)? Does the browser snippet work under a strict CSP (inline script, nonce, connect-src)? What fraction of production HTML is served from cache? Is there an export path before analytics:prune deletes data?
Stack fit. Laravel 12 and 13, PHP 8.3–8.5, MySQL 8.4 / PostgreSQL 17 / SQLite — matches a current, well-maintained stack and shows the maintainer tracking Laravel's release cadence closely (Laravel 13 support at first tag). Non-fits: PHP 8.2 or older Laravel, Lumen/Symfony, and anything where the "web" group isn't the page-serving path.
Migration path. Purely additive: no changes to existing tables, no interception of existing flows, no data export required to start. The one-way door is the data itself — adopting means owning a growing pageviews/visits/events dataset inside your own database indefinitely. Decide retention (retention_days) before the first install rather than after the table has grown, and decide whether the data belongs in the app's primary database at all.
If this replaces GA4 or Plausible, run both in parallel for two to four weeks and reconcile before switching anything off. Expect this tool to report lower pageviews — DNT/GPC compliance, bot filtering, no-cookie visit stitching, skipped Livewire transitions, cache-served pages. Agree the acceptable delta with whoever consumes the numbers, or the first traffic review becomes a debate about instrumentation.
Compatibility. Pin the exact version in composer.json for now. Add the package to your Laravel upgrade checklist: a framework major should not ship before the analytics package supports it, and you do not want to be the one discovering that on upgrade day. Add a smoke test in your own suite that a tracked route writes a row.
Sequencing.
identify()/user-linking path; draft policy text and set retention_days.viewFirstPartyAnalytics gate explicitly per environment — who sees it, behind what auth and network boundary.analytics:prune into the scheduler and alerting.Maintenance. Low code footprint, small maintainer team (one), pre-1.0, no published support window or security-disclosure route. The engineering hygiene signals are strong for a 0.1 — 86 Pest tests across a PHP 8.3/8.4/8.5 × Laravel 12/13 matrix, PHPStan level 6, Pint, an install verified in a real app in a real browser. That reduces but does not remove the bus-factor risk. Assume you may need to carry a patch; keep the fork path open by not building app logic on top of the package's internals, only on the facade and the documented config.
Support. Community-only, no SLA. Name an internal owner for the three recurring questions: who reads the dashboard, who tunes retention, and who investigates "traffic looks wrong." Without that, the first anomaly becomes an unowned investigation. Note also that the numbers are now your numbers — there is no vendor to blame for a counting discrepancy.
Scaling. The write path is one pageview per HTML response, batched after the response by default. On the primary database that is write amplification competing with transactional traffic; at meaningful volume, use a separate connection and confirm the pageviews table is indexed for the dashboard's date-range and grouping queries. The prune command deletes in chunks, which is the right design, but its runtime grows with data — run it off-peak and monitor it. The browser event endpoint is public by design; same-origin checks and rate limiting are the right controls, but the rate limiter needs a shared cache store in a multi-node deployment or the effective limit multiplies by node count. Put the endpoint behind the same CDN/WAF as the rest of the app.
Failure modes.
--days run and you lose history you cannot reconstruct. --pretend exists; use it in the first manual runs.Ramp-up. Low for a Laravel developer: facade, one Blade directive, one config file, two artisan commands — half a day to install and see data. Medium for the surrounding work, which is where the effort actually is: gate definition, retention policy, log alerting, privacy review of the identified-user path, and the parallel-run reconciliation. Plan one to two engineering days to productionize, plus legal/privacy review time, and treat the dashboard gate and retention window as launch blockers rather than follow-ups.
Overall. Worth a pilot, not yet a default. The architecture is right for a Laravel monolith that wants first-party, privacy-respecting traffic and conversion data without extra infrastructure, and the privacy defaults are unusually thoughtful. The blockers to a broader rollout are the pre-1.0 API, the single-maintainer support model, tenant scoping (if relevant to you), and the consent/retention decisions that only your organisation can make. Adopt behind a pinned version, with a named owner, an explicit retention policy, and a confirmed answer on caching and the queue worker.
How can I help you explore Laravel packages today?