Search by

justin991q / matomo-server-side-tracking

Justin991q

Matomo server-side tracking module for Laravel

Package info

gitlab.com/Justin991q/laravel-matomo-server-side-tracking

pkg:composer/justin991q/matomo-server-side-tracking

Statistics

Installs: 3

Dependents: 0

Suggesters: 0

Stars: 0

1.0.0 2026-10-25 13:25 UTC

This package is auto-updated.

Last update: 2026-10-25 12:28:44 UTC


README

A Laravel module for server-side Matomo tracking and client-side JavaScript injection.

Requirements

  • PHP 8.3+
  • Laravel 11, 12 or 13

Installation

The package isn't published on Packagist, add one of the following repositories to your composer.json first.

GitLab package registry

Tagged releases are published to the GitLab Composer package registry, which is accessed at the group level:

"repositories": [
    {
        "type": "composer",
        "url": "https://gitlab.com/api/v4/group/<group_id>/-/packages/composer/packages.json"
    }
]

The registry requires authentication, use a personal access token (read_api scope) or a deploy token (read_package_registry scope):

composer config gitlab-token.gitlab.com <token>

Git repository

"repositories": [
    {
        "type": "vcs",
        "url": "https://gitlab.com/Justin991q/laravel-matomo-server-side-tracking.git"
    }
]

Then require it:

composer require justin991q/matomo-server-side-tracking

The MatomoServiceProvider and the Matomo facade alias are registered automatically via package discovery.

Configuration

Add the following variables to your .env file:

MATOMO_ENABLED=true
MATOMO_URL=https://stats.example.com
MATOMO_SITE_ID=1
MATOMO_TOKEN_AUTH=your-token-here

Core

VariableRequiredDefaultDescription
MATOMO_ENABLEDNofalseEnable or disable all tracking
MATOMO_URLYes—Base URL of your Matomo instance
MATOMO_SITE_IDNo1Matomo site ID
MATOMO_TOKEN_AUTHYes*—Auth token — required for server-side IP forwarding (cip)
MATOMO_TIMEOUTNo5HTTP request timeout in seconds
MATOMO_JS_TRACK_PAGE_VIEWNotrueWhether the JS snippet calls trackPageView

MATOMO_TOKEN_AUTH is required for the server-side tracker to forward the visitor's real IP. Without it, all requests will appear to originate from your server.

When MATOMO_ENABLED=true and MATOMO_URL is empty, tracking throws a MatomoException.

Queue

Tracking requests are dispatched to a queue by default so they never block the HTTP response.

VariableRequiredDefaultDescription
MATOMO_QUEUENotrueDispatch tracking requests via the queue
MATOMO_QUEUE_NAMENomatomoQueue name for tracking jobs
MATOMO_QUEUE_CONNECTIONNoQUEUE_CONNECTIONQueue connection (defaults to the app's default connection)

To process the queue, run a worker targeting the matomo queue:

php artisan queue:work --queue=matomo

To send tracking requests synchronously instead (blocking, no worker required):

MATOMO_QUEUE=false

When the queue is enabled, all tracking methods always return true immediately. Tracking is best-effort: the SendTrackingRequest job catches all exceptions, so failed requests are dropped and never retried or marked as failed.

Request headers (user agent, language, referrer) can contain malformed UTF-8, which can't be JSON encoded into the queue payload. Invalid byte sequences in all string parameters are replaced with ? before the request is queued or sent.

Exclusions

VariableRequiredDefaultDescription
MATOMO_EXCLUDED_IPSNo—Comma-separated IP patterns to exclude (supports * wildcard)
MATOMO_EXCLUDED_PAGESNo_tt*,telescope/*,admin/*Comma-separated path patterns to exclude (supports * wildcard)
MATOMO_FORCE_SERVER_SIDE_ROUTESNo—Comma-separated route name patterns that always track server-side, even when the JS cookie is present (supports * wildcard)
MATOMO_EXCLUDED_IPS=192.168.1.*,10.0.0.1
MATOMO_EXCLUDED_PAGES=_tt*,telescope/*,admin/*,health
MATOMO_FORCE_SERVER_SIDE_ROUTES=files.*,downloads.show

Avoiding Duplicate Page Views

When both the TrackPageView middleware and the JS snippet are active, page views are recorded twice — once server-side and once by the browser.

Option A — Server-side owns all page view tracking (recommended):

Set MATOMO_JS_TRACK_PAGE_VIEW=false. The JS snippet still loads and calls enableLinkTracking, so outbound link clicks and downloads continue to be tracked. Only trackPageView is removed from the JS snippet.

MATOMO_JS_TRACK_PAGE_VIEW=false

Option B — JS owns page view tracking, server-side is fallback:

Leave MATOMO_JS_TRACK_PAGE_VIEW=true (the default). The middleware checks for the Matomo cookie (_pk_id.*) set by the JS tracker. If the cookie is present, the browser has already tracked the page view and the middleware skips. If the cookie is absent (ad-blocker, first visit before JS runs), the middleware tracks as a fallback.

This is read directly from $_COOKIE so it works regardless of Laravel's cookie encryption configuration.

Usage

Client-Side JavaScript (Blade view)

Include the tracking snippet in your layout. It renders nothing when MATOMO_ENABLED is false:

@include('matomo::tracking')

Pass a CSP nonce to add it to the <script> tag:

@include('matomo::tracking', ['csp' => app('csp-nonce')])

The snippet sets the authenticated user's email as the Matomo user ID when a session is active. The server-side tracker does the same (uid) for trackPageView and trackRequest.

Server-Side Tracking

Inject MatomoTracker or use the Matomo facade.

Track a page view:

use Justin991q\MatomoServerSideTracking\MatomoTracker;

app(MatomoTracker::class)->trackPageView($request, url: 'https://example.com/page', actionName: 'Page Title');

url defaults to the request's full URL and actionName to the request path.

Track an event:

use Justin991q\MatomoServerSideTracking\Facades\Matomo;

Matomo::trackEvent('Downloads', 'click', 'report.pdf', 1.0);

Events don't take a request, so no visitor data (IP, user agent, visitor ID) is sent unless you pass it via $options.

Track from a Laravel Request (auto-extracts IP, user agent, language, user ID, visitor ID):

Matomo::trackRequest($request, 'Optional Page Title');

All methods accept an $options array as the final argument for any additional Matomo Tracking API parameters:

Matomo::trackPageView($request, options: [
    'dimension1' => 'premium',
    'gt_ms'      => 250,
]);

When the queue is disabled, methods return bool — true on success, false on failure (connection error, HTTP 5xx). Failures are silent and will never throw or break the application. When the queue is enabled, methods always return true.

Visitor ID Resolution

The server-side tracker resolves visitor IDs in this order:

  1. Reads the Matomo cookie (_pk_id_{site_id}_*) from $_COOKIE. PHP normalises the dots in the original cookie name (_pk_id.1.abc) to underscores, so this is read directly from the PHP superglobal.
  2. Falls back to an MD5 hash of the visitor's IP address.

Force Server-Side Tracking on Specific Routes

Use MATOMO_FORCE_SERVER_SIDE_ROUTES to always track certain routes server-side, even when the JS cookie is present. This is useful for routes that are critical to track regardless of ad-blockers or duplicate-prevention logic:

MATOMO_FORCE_SERVER_SIDE_ROUTES=files.*,downloads.show

Supports * wildcards matching against the Laravel route name. Unnamed routes are never matched.

Middleware

TrackPageView automatically tracks all non-JSON GET requests when MATOMO_ENABLED=true. Register it in your route groups or global middleware stack:

// bootstrap/app.php
->withMiddleware(function (Middleware $middleware) {
    $middleware->append(\Justin991q\MatomoServerSideTracking\Middleware\TrackPageView::class);
})

The middleware skips:

  • Non-GET requests
  • JSON requests (Accept: application/json)
  • Requests from excluded IPs (MATOMO_EXCLUDED_IPS)
  • Requests matching excluded page patterns (MATOMO_EXCLUDED_PAGES)
  • Requests with the JS tracker cookie, unless MATOMO_JS_TRACK_PAGE_VIEW=false or the route matches MATOMO_FORCE_SERVER_SIDE_ROUTES (see Avoiding Duplicate Page Views)

Module Structure

├── config/
│   └── matomo.php
├── resources/
│   └── views/
│       └── tracking.blade.php          # Client-side JS snippet
├── Exceptions/
│   └── MatomoException.php
├── Facades/
│   └── Matomo.php
├── Jobs/
│   └── SendTrackingRequest.php         # Queued HTTP dispatch
├── Middleware/
│   └── TrackPageView.php
├── tests/
│   ├── MatomoServerSideTrackingTest.php
│   └── TestCase.php                    # Testbench standalone, the host app's TestCase otherwise
├── MatomoServiceProvider.php
├── MatomoTracker.php
└── composer.json

Testing

composer install
composer test

The GitLab CI pipeline runs the test suite on PHP 8.3, 8.4 and 8.5 against Laravel 11 (PHP 8.3 and 8.4 only), 12 and 13.

Releasing

Push a semver tag, after the tests pass the pipeline publishes it to the GitLab package registry:

git tag v1.0.0
git push origin v1.0.0

License

The MIT License (MIT). See LICENSE for details.