Skip to main content

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.