Jarhead for Craft CMS

Configuration

Every setting is available on Settings → Plugins → Jarhead and overridable from config/jarhead.php.

Site IDs

One Hotjar Site ID per Craft site, keyed by site handle:

// config/jarhead.php
return [
    'siteIds' => [
        'default' => '$HOTJAR_SITE_ID',
        'german'  => '$HOTJAR_SITE_ID_DE',
    ],
];

Values are read through Craft's environment parsing, so production and staging can point at different Hotjar accounts — or staging at none. An empty or missing value means Hotjar is off for that site. That is the off switch; there is no second one, because "configured but disabled" is a state nobody can see.

SettingDefault
enabledtrueMaster switch. Off, Jarhead emits nothing anywhere
siteIds[]Site ID per Craft site handle
snippetVersion6Hotjar's hjsv. Here so a version bump needs no release

Where the code goes

SettingDefault
autoInjecttrueSplice into front-end HTML automatically
injectionPoint'head'head or body
scriptNonce''CSP nonce, usually an environment variable

Hotjar asks to be in the head, and it matters more than it sounds: the tracking code is what starts the recording, so anything that happens before it is part of the session nobody can watch. A page with no <head> falls back to the body either way.

Turn autoInject off to place the code yourself:

{{ craft.jarhead.snippet }}

Calling that tag stands automatic injection down for the page, so it is safe to add to a layout while leaving the setting on.

Content Security Policy

A CSP is the single most common reason a correctly installed tracking code does nothing — the browser reports the block to a console nobody is looking at. Utilities → Hotjar prints the directives Hotjar needs:

script-src  https://static.hotjar.com https://script.hotjar.com 'unsafe-inline';
connect-src https://*.hotjar.com https://*.hotjar.io wss://*.hotjar.com;
img-src     https://*.hotjar.com data:;
font-src    https://*.hotjar.com;
style-src   'unsafe-inline';

Set scriptNonce and you can drop 'unsafe-inline' from script-src — the nonce is put on Jarhead's own script tag and on the Hotjar script it creates.

Environments

SettingDefault
allowedEnvironments[]Empty means all of them
trackInDevModefalse

Matched against CRAFT_ENVIRONMENT. See Installation for why filling this in matters more than anything else here.

Who not to track

SettingDefault
excludePreviewstrueLive preview, drafts, share tokens
excludeAdminstrue
excludeLoggedInfalseBlunt; right for a marketing site
excludedGroups[]User group handles
uriRules[]See below
excludeAutomatedBrowserstruenavigator.webdriver

The control panel is never tracked, and there is no setting for it. A session recorder pointed at the control panel records other people's addresses, order histories and account details. That is a data breach with a subscription.

URI rules

'uriRules' => [
    ['pattern' => 'checkout/*', 'mode' => 'exclude'],
    ['pattern' => 'account/*',  'mode' => 'exclude'],
],

Patterns are globs by default, regular expressions when prefixed with re:, and __home__ matches the homepage.

An exclusion always beats an inclusion, whatever the order — these are read as "everywhere, except there". Write only exclusions and everything else stays tracked; write a single inclusion and nothing outside the inclusions is tracked.

Automated browsers

excludeAutomatedBrowsers refuses to run when navigator.webdriver is set — Playwright, Puppeteer, Selenium and most uptime checkers. It is decided in the visitor's browser rather than from the user agent, because a user-agent test on the server would vary your page cache.

Consent

SettingDefault
consentMode'off'off, cookie, event, dataLayer, manual
consentCookie''Cookie mode: the cookie name
consentCookieValue''Comma separated; substring: prefix supported
consentEvent'jarhead:consent'Event mode: the DOM event
consentDataLayerKey'analytics_storage'Google Consent Mode v2
consentDataLayerValue'granted'
consentPollInterval500Milliseconds, cookie mode only
consentTimeout0Seconds; 0 watches for the life of the page
honourDnttrueDo Not Track
honourGpctrueGlobal Privacy Control

Under a consent gate, nothing reaches Hotjar until the visitor agrees: no script element, no request to static.hotjar.com, no _hjSettings. Not a blocked script and not a deferred one — the tracking code is created by the gate, in the browser, after consent. (The gate itself is in the page, holding Hotjar's URL as a string it has not used.)

Consent is decided in the browser, not on the server, because a cookie read server-side would vary the page cache — and a cached "yes" served to the next visitor is worse than no gate at all.

'consentMode'        => 'cookie',
'consentCookie'      => 'cookie_consent',
'consentCookieValue' => 'substring:analytics',

substring: matches anywhere inside the value, which is how most consent managers behave — they store every category in one cookie and expect you to look for yours inside it. Leave consentCookieValue empty and the cookie simply existing counts as consent.

For manual, nothing happens until your code calls window.jarhead.consent().

Do Not Track and Global Privacy Control are honoured on top of whichever gate you pick, and a refusal beats consent: consenting does not override a visitor's DNT.

Attributes

SettingDefault
autoAttributessite, section, entry type, environment
customAttributes[]['name' => …, 'value' => …]
identifyUsersfalseSend the Craft user ID
hashUserIdstrueHMAC it with your security key
spaSupportfalseReport route changes

Available automatic attributes: craft_site, craft_section, craft_entry_type, craft_template, craft_logged_in, craft_user_group, craft_environment, craft_language. The craft_ prefix is deliberate — attribute names are a flat namespace shared with every other integration sending to the same Hotjar site.

'autoAttributes'    => ['craft_site', 'craft_section', 'craft_entry_type', 'craft_template'],
'customAttributes'  => [
    ['name' => 'release', 'value' => '$RELEASE_TAG'],
],

Custom values are read through the environment too, so a build number or release tag can be an attribute without being committed.

There is no setting anywhere that sends an email address or a username. A Hotjar account is not the right home for either, and an identifier only has to be stable to be useful — which is why identifyUsers sends the Craft user ID, HMAC'd with your security key by default.