Custom dimensions and content tracking
Two free Matomo core features that both work from either side of the integration. Custom Dimensions attach your own fields to a hit. Content Tracking measures whether a block was actually seen and whether it was acted on.
Custom dimensions, client-side
The map mirrors the server-side dimension numbering, and its values are set on every page view:
'js' => [
'custom_dimensions' => [1 => 'member', 3 => env('APP_ENV')],
],
Values here are static for the duration of a request, so this is the right place for things that describe the visitor or the deployment — the plan they are on, the environment, a feature-flag cohort. The snippet pushes them before the page view, so action-scoped dimensions attach to it, and re-applies them on each virtual page view.
Keys must be positive integers matching the dimension ids you configured in Matomo. A non-integer or non-positive key is skipped rather than sent as a broken parameter.
Custom dimensions, server-side
Server-side you decorate an individual hit, so the value can be different for every one:
use MatomoAnalytics\Facades\Matomo;
use MatomoAnalytics\Tracking\CustomParameters;
use MatomoAnalytics\Tracking\PageView;
Matomo::track(
CustomParameters::for(new PageView('Dashboard'))
->dimension(1, 'plan:pro') // dimension1=plan:pro
->param('_rcn', 'newsletter') // any raw Tracking-API parameter
);
CustomParameters wraps any hit type — a page view, an event, an ecommerce
order. Three properties are worth knowing:
- It is immutable. Every builder call returns a new instance, so a decorated hit is safe to hold onto and reuse.
- It never nests. Decorating an already-decorated hit returns the same instance, so a helper that decorates defensively cannot produce a double wrapper.
- A custom parameter wins. If you set a parameter the wrapped hit also sets, yours overrides it.
The dimension's scope — action or visit — is defined by the dimension id in Matomo, not here. The package sends a value against a number; what that number means is Matomo's configuration.
The raw-parameter escape hatch
->param() reaches any Tracking-API parameter the typed hits do not model —
campaign attribution (_rcn, _rck), a generation time, anything Matomo accepts.
This is the seam that keeps you from having to fork the package for one missing
field. It is also unchecked: a misspelled parameter name is silently ignored by
Matomo, so verify a new one against Matomo's Tracking API reference and confirm it
arrives.
Content tracking, client-side
Turn on automatic impression tracking for blocks marked up with
data-track-content:
'js' => [
'content_tracking' => 'visible', // false | 'all' | 'visible'
],
'all' scans every content block on the page. 'visible' records an impression only
when a block is actually scrolled into the viewport, which is usually what you want:
"was this promo seen?" is a different question from "was it in the HTML?".
The scan is pushed after the page view, and re-run on each soft navigation so blocks revealed by a client-side route change are counted too.
Content tracking, server-side
For blocks whose exposure you determine server-side — an email-rendered banner, a personalization decision made in PHP — record them directly:
Matomo::contentImpression('Promo banner', piece: 'summer.jpg', target: 'https://example.com/sale');
Matomo::contentInteraction('click', 'Promo banner', piece: 'summer.jpg');
name identifies the block, piece the specific creative shown, and target where
it points. For an interaction, the first argument is what happened — click is the
convention Matomo's reports assume.
Impressions and interactions are the two halves of the same report: the interaction rate is what tells you whether a block that was seen actually worked.
Reading them back
Both features have read-side helpers:
MatomoReports::customDimension($idDimension), MatomoReports::contentNames() and
MatomoReports::contentPieces(). See reporting.