Client-side tracking
The client-side tracker sees what the server cannot: how long someone stayed, what they clicked, their screen and browser, and the page-performance timings Matomo's own report is built from. One Blade directive renders it.
<head>
{{-- ... --}}
@matomoScript
</head>
That is the whole integration. The directive renders an empty string unless the
package is enabled, js.enabled is true and the instance is configured — so it
is safe to drop into a layout that also serves local development and CI, with no
@if around it.
What it emits
By default: a dns-prefetch link for the Matomo origin, the _paq bootstrap, the
asynchronous loader for matomo.js, and a <noscript> fallback pixel so a visitor
without JavaScript still produces a page view.
The order of the _paq commands is deliberate, because Matomo applies some of them
only if they arrive before the page view:
disableCookies— whenprivacy.cookielessis true (the default).- The consent call for your
privacy.consentposture, if any. setDoNotTrack— whenprivacy.honor_dntis true (the default).- Any statically configured custom dimensions.
disablePerformanceTracking— only whenjs.performanceis false.trackPageView.- Content-impression scanning, if content tracking is on.
enableLinkTracking, then the heartbeat timer.- The tracker URL and site id, then the loader.
- The soft-navigation listeners, if
spa.enabled.
Every value the package embeds in that script is JSON-encoded as a JavaScript string literal, or HTML-escaped when it lands in an attribute.
The settings
'js' => [
'enabled' => env('MATOMO_JS_ENABLED', true),
'host' => env('MATOMO_JS_HOST'), // separate asset host, e.g. a Matomo Cloud CDN
'tag_manager' => null, // full MTM container URL
'enable_link_tracking' => true,
'performance' => true,
'custom_dimensions' => [],
'content_tracking' => false,
'heartbeat' => 15, // enableHeartBeatTimer seconds; 0 to disable
'noscript' => true,
'dns_prefetch' => true,
],
enabled turns the client tracker off as a whole, and it reads from the
environment so that a deployment whose config file is template-managed can still say
so — MATOMO_JS_ENABLED=false. That matters for the failure mode a config edit has
there: the next template sync reverts it silently, so the switch reads "off" until
one day it does not, and nothing announces the change.
It governs both doors that put tracking into a page:
| Directive | Governed by | Notes |
|---|---|---|
@matomoScript | js.enabled | the tracker itself |
@matomoNoscript | js.enabled | a tracking pixel that works with JavaScript off |
@matomoWebVitals | web_vitals.enabled | its own switch, and it ships off |
@matomoOptOut | — | not tracking; it is the opt-out iframe and keeps working |
The table is here because the list is easy to get wrong from memory: a consuming
project's guard named two of the four, and the two it missed were the <noscript>
pixel and the Web Vitals reporter — both real doors. If you are auditing which
directives a codebase uses, read the registered list rather than writing it out.
heartbeat is what makes time-on-page meaningful. Without it Matomo can only
measure the interval between two page views, so the last page of every visit counts
as zero seconds. Fifteen seconds is a reasonable default; 0 disables it.
enable_link_tracking records outbound links and file downloads automatically.
It is pushed after the page view, as Matomo requires.
noscript renders a tracking pixel for visitors without JavaScript. The pixel
carries referrerpolicy="no-referrer-when-downgrade".
One caveat if you validate your markup, and it is the only reason the pixel can be
split out: inside <head>, the HTML specification allows a <noscript> element to
contain only <link>, <style> and <meta>. An <img> there is a parse error.
Browsers recover from it — the parser's "after head" handling pushes the following
head elements back where they belong — so the practical effect is a validator
complaint, not a broken page, and only for visitors with JavaScript switched off.
If you would rather have neither, place the pixel in the <body>, where an <img>
is legal. It is a two-step change:
// config/matomo-analytics.php
'js' => [
'noscript' => false, // stop @matomoScript emitting the pixel in <head>
],
<body>
{{-- ...your page... --}}
@matomoNoscript
</body>
@matomoNoscript renders exactly the same pixel and, like every other part, renders
nothing at all when tracking is inactive. Leaving noscript at true and also
placing the directive would emit it twice, so pick one.
dns_prefetch emits a resolver hint for the Matomo origin — and a second one
for the asset host when js.host points somewhere else.
host loads matomo.js from a different origin (typically a Matomo Cloud CDN)
while tracking stays on your own subdomain. See Matomo Cloud.
Content-Security-Policy nonce
Pass your nonce and it is applied to every script tag the directive renders:
@matomoScript($cspNonce)
Tag Manager instead of the tracker
Set js.tag_manager to a container URL and the directive renders the Matomo Tag
Manager bootstrap (_mtm) instead of the direct tracker:
'js' => [
'tag_manager' => 'https://analytics.example.com/js/container_AbCdEf12.js',
],
Everything the container does is configured in Matomo's Tag Manager UI rather than here, and soft-navigation tracking becomes Tag Manager's job — it has its own history-change trigger.
Four of the js settings still apply, because they are not _paq pushes and
have nothing to bootstrap against:
| Setting | Under Tag Manager |
|---|---|
js.enabled | Still the master switch. MATOMO_JS_ENABLED=false renders nothing at all, container included. |
js.noscript | Still emits the <noscript> pixel. This is deliberate: a visitor without JavaScript never loads the container either, so the pixel is the only thing that counts them. It hits matomo.php directly rather than going through the container — set 'noscript' => false if you would rather have no second path. |
js.dns_prefetch | Still emits the <link rel="dns-prefetch"> hints. |
js.host | Still chooses the host those hints name. It no longer selects where a script is loaded from — the container URL is js.tag_manager in full. |
The remaining six — enable_link_tracking, performance, custom_dimensions,
content_tracking, heartbeat and the tracker half of js.host — are _paq
pushes, and under Tag Manager there is no _paq bootstrap to push them onto.
Configure those behaviors in the container instead.
Server-side tracking is unaffected either way.
The opt-out control
@matomoOptOut
This renders Matomo's own opt-out iframe, which lets a visitor opt out of tracking by this Matomo instance. It renders nothing when the instance is not configured.
The iframe is served by Matomo, so the cookie it sets lives on the Matomo domain — which server-side tracking cannot see. If you track server-side as well, pair it with the first-party opt-out cookie described in consent and redaction.
Also on the client side
- SPA and soft navigation — virtual page views for Livewire, Inertia and History-based routers.
- Page performance — what
js.performancecontrols. - Web Vitals — the separate
@matomoWebVitalsdirective.