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.
composer require amjadiqbal/laravel-first-party-analytics
php artisan migrate
php artisan about # confirms the package registered
There is nothing else to configure for a first data point: the tracking middleware is auto-added to the web group, so loading any successful HTML page on a web route writes a pageview and a visit. Open the dashboard (local-only by default — see the gate below) and confirm the hit appears before you touch config.
Order of operations for a first real setup:
ANALYTICS_COOKIES=true only if you need cross-day visitor continuity and anonymous-visit linking on login. This choice changes what your funnel numbers mean, so make it before you accumulate history.exclude_paths, retention_days, ignore_referrers, conversions, and funnel definitions.viewFirstPartyAnalytics, local-only by default — define it in a service provider (Gate::define('viewFirstPartyAnalytics', fn ($user) => $user->isAdmin())) or your staging team will report "it's broken".analytics:prune to the scheduler the same day. Retention is opt-in via that command; without it the tables grow forever.First meaningful use case: drop exclude_paths entries for /admin, /health, /sitemap.xml, and any webhook-ish GET routes, then let it run for a day before wiring custom events.
Tracking scope. For most apps, leave the auto-web-group registration alone and control scope with exclude_paths. Reach for the analytics.track middleware alias only when you want tracking on routes outside web or want per-group opt-in instead of per-route opt-out.
Custom events from PHP. Analytics::track('signup_completed', ['plan' => 'pro']) inside a controller joins the visit already created for that request, so the event lands attached to the right visit with no extra correlation work. From a queued job or artisan command there is no request context — call Analytics::identify($user) first, or the event is stored against the user but not a visit. This is the pattern for "trial expired", "invoice paid", anything asynchronous.
Browser events. Include @firstPartyAnalytics once (layout, before </body>). Then either analytics.track('cta_click', { position: 'hero' }) or, for zero-JS cases, data-analytics-event="cta_click" on the element. Clicks are sent with sendBeacon to a same-origin-checked, rate-limited endpoint — best-effort by design.
Identity. User linking is automatic on Login and Registered. If you use a custom auth flow (SSO, passwordless, impersonation), call Analytics::identify() yourself. With cookies enabled, identify() also back-links the visitor's earlier anonymous visits — that is the main reason to accept cookies.
Funnels and conversions. Define them in config as ordered event sequences rather than computing them in SQL later. Because a visit ends on a new source, keep funnel steps inside a single-session journey (e.g. view_pricing → start_checkout → purchase), not "signed up last month, upgraded today".
Write mode. Default batched-after-response is right for almost everyone. Choose the queued mode when per-request write latency or DB contention matters, and make sure a worker is running. Use sync only in tests or while debugging a missing event.
Suppression. Analytics::ignoreUsing(fn () => ...) for admin users, internal IPs, or a ?preview=1 flag — cheaper and more reliable than trying to express every case in exclude_paths.
Tests. Feature tests hitting web routes will write analytics rows. Disable tracking in your test suite config (or via ignoreUsing) so you don't assert against polluted tables.
utm_* are captured as their own columns, and ?ref= is supported, but arbitrary params on the referrer URL are gone. Keep campaign tags on your own landing URLs, where they are read.ignore_referrers defaults cover Stripe Checkout, PayPal, Google and Apple sign-in. Any other OAuth/checkout provider (Auth0, GitHub, Clerk, a custom payment page) will look like a new source and split the visit, breaking funnel completion. Add yours.Analytics::track() is the durable path.analytics:prune --pretend first, schedule it off-peak, and confirm retention_days matches whatever your privacy policy claims.DetectsBots contract (swap for your own bot logic without touching the middleware), Analytics::ignoreUsing(), the viewFirstPartyAnalytics gate, and the write-mode config. Everything else is driven by config.How can I help you explore Laravel packages today?