Laravel
Server-side pageview tracking for Laravel 11 and 12.
The Laravel package tags every HTML response with a request id and queues a single, minimal event onto the application's own queue after the response has been sent. The request is never blocked and nothing is sent inline.
Requirements
- PHP 8.2+
- Laravel 11 or 12
- A working queue (the
syncdriver works too, but then the POST happens during the request)
Installation
composer require metrikstack/laravelThe service provider is auto-discovered. Publish the config if you want to edit it:
php artisan vendor:publish --tag=metrikstack-configSet the environment variables:
METRIKSTACK_TOKEN=your-site-token
METRIKSTACK_ENDPOINT=https://ingest.example.com
# optional
METRIKSTACK_ENABLED=true
METRIKSTACK_QUEUE_CONNECTION=redis
METRIKSTACK_QUEUE=metrikstack
METRIKSTACK_TRUST_PROXY="*"The site id is derived from the token server-side, so there's nothing else to configure.
Middleware
The provider pushes MetrikStack\Laravel\Middleware\TrackPageview onto the
web middleware group, so a plain composer require starts tracking.
To register it yourself instead, drop it from the group and add it
explicitly in bootstrap/app.php:
->withMiddleware(function (Middleware $middleware) {
$middleware->web(append: [
\MetrikStack\Laravel\Middleware\TrackPageview::class,
]);
})Only requests that satisfy all of the following are tracked:
- method
GETorHEAD Acceptheader containstext/html- the path is not an asset: not ending in
.js .css .png .jpg .jpeg .gif .svg .ico .woff .woff2 .ttf .map .json .xml .txt, and not under/_debugbar,/storage,/vendor,/build,/assets,/static
Both lists are configurable via ignore_extensions and ignore_paths.
Blade
Add the meta tag to your layout's <head>:
<head>
<meta charset="utf-8">
@metrikstackMeta
...
</head>It renders <meta name="metrikstack-request-id" content="..."> and nothing at
all on pages that aren't tracked. The id is also shared with every view as
$metrikstackRequestId if you'd rather render it yourself.
Custom events
use MetrikStack\Laravel\Facades\MetrikStack;
MetrikStack::track('signup', ['plan' => 'pro']);Custom events are queued the same way and, when raised during a request, carry that request's id so they line up with the pageview.
Why the request id matters
A pageview can be observed by more than one source: this package on the
server and the browser script in the page. Both send the same request_id,
so they are merged into one pageview instead of two (see How merging
works). The id reaches the browser two ways, so it survives
most setups:
<meta name="metrikstack-request-id">, from@metrikstackMetaServer-Timing: metrikstack;desc=<id>, read by the script viaperformance.getEntriesByType('navigation')[0].serverTiming, which works through CDNs where the meta tag might be cached away
The response also carries X-MetrikStack-Request-Id for debugging and log
correlation.
What is sent
Exactly these fields, and nothing else:
| Field | Source |
|---|---|
name | pageview, or your custom event name |
url | full URL, query string stripped to utm_* and ref only |
referrer | Referer header |
request_id | UUIDv7 generated per HTML response |
ts | RFC 3339 timestamp |
status_code | response status |
method | GET / HEAD |
ip | $request->ip(), hashed and discarded at ingest |
user_agent | User-Agent header |
accept_language | Accept-Language header |
Never sent: cookies, session data, form/request bodies, headers beyond the three above, route parameters, authenticated user identity, or the full query string.
IP and proxies
ip is whatever $request->ip() returns. Behind a load balancer set
METRIKSTACK_TRUST_PROXY to * (trust the immediate peer) or to a
comma-separated list of proxy IPs/CIDRs. Leave it unset to use the
application's own TrustProxies configuration untouched. The IP is hashed
at ingest and the raw value discarded.
Delivery
MetrikStack\Laravel\Jobs\SendEvents POSTs {"events": [...]} to
{endpoint}/v1/batch with Authorization: Bearer <token>, a 5 s HTTP
timeout, 2 tries and a [5, 30] second backoff. After that the batch is
dropped: analytics must never take the application's queue down with it.
Nothing is sent when enabled is false or the token is empty. The request
id headers are still set, so the browser script keeps working.