MetrikStack
Sources

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 sync driver works too, but then the POST happens during the request)

Installation

composer require metrikstack/laravel

The service provider is auto-discovered. Publish the config if you want to edit it:

php artisan vendor:publish --tag=metrikstack-config

Set 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 GET or HEAD
  • Accept header contains text/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 @metrikstackMeta
  • Server-Timing: metrikstack;desc=<id>, read by the script via performance.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:

FieldSource
namepageview, or your custom event name
urlfull URL, query string stripped to utm_* and ref only
referrerReferer header
request_idUUIDv7 generated per HTML response
tsRFC 3339 timestamp
status_coderesponse status
methodGET / HEAD
ip$request->ip(), hashed and discarded at ingest
user_agentUser-Agent header
accept_languageAccept-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.

On this page