Jarhead for Craft CMS

Troubleshooting

The failure mode of any tracking integration is silence, and silence looks exactly like success. Two tools exist to break that tie: php craft jarhead/explain <uri> and Utilities → Hotjar. Start with whichever is closer to hand.

There is no tracking code in the page source

Ask directly:

php craft jarhead/explain              # the homepage
php craft jarhead/explain blog/hello   # any URI

It answers with the first rule that applied. In rough order of how often each one is the culprit:

ReasonWhat it means
dev-modedevMode is on and trackInDevMode is off. The default.
adminYou are signed in as an admin. Admins are excluded by default.
environmentThis environment is not in allowedEnvironments.
no-site-idNo Site ID resolves for this Craft site in this environment.
previewA live preview, draft URL or share token.
uri-excludedThe URI matched an exclusion rule — the message names which.
uri-not-includedInclusion rules exist and this URI matches none of them.
logged-inexcludeLoggedIn is on.
user-groupThe user is in an excluded group.
control-panelThe control panel is never tracked. Not configurable.
disabledThe master switch is off.

The order matters: you are told the first reason that applies, so fixing it may reveal another.

"No Site ID" when you are sure you set one

The value is probably an environment variable that is not set here. An unresolved $HOTJAR_SITE_ID reads back as "not configured in this environment" rather than as a literal string, which is correct behaviour and the whole reason the setting is env-aware.

Utilities → Hotjar distinguishes the two: a site whose ID comes from an env var shows the variable name and unset here, rather than not configured.

The code is in the page but there are no recordings

Open the browser console on a front-end page:

window.jarhead.status()
blockedByMeaning
null and booted: trueJarhead did its job. The problem is on Hotjar's side — keep reading.
'dnt'The visitor sends Do Not Track and honourDnt is on.
'gpc'The visitor sends Global Privacy Control.
'automated'navigator.webdriver is set — you are in Playwright, Puppeteer or Selenium.
'consent-timeout'consentTimeout elapsed before consent arrived.
null and booted: falseA consent gate is open and waiting. See below.

If it booted and Hotjar still shows nothing, press Verify in Utilities → Hotjar. The two answers that catch most cases:

  • "Session recording is switched off for this site in your Hotjar account." Nothing about the installation is wrong. Turn recording on in Hotjar.
  • "Hotjar is sampling N% of sessions." You are recording a fraction of traffic by design.

An automated browser is the answer more often than you would think

If you are testing with Playwright or a headless Chrome, excludeAutomatedBrowsers is doing exactly what it was asked to. Turn it off temporarily, or test in a real browser.

The consent gate never opens

window.jarhead.status() shows booted: false with blockedBy: null — the gate is waiting.

Cookie mode. Check the cookie is actually there and actually matches:

document.cookie                    // is the cookie present at all?
window.jarhead.status().consentMode

The most common cause is a value mismatch. A consent manager that stores every category in one cookie needs substring:substring:analytics rather than analytics. Leave consentCookieValue empty and the cookie merely existing counts as consent.

dataLayer mode. Existing entries are checked and later pushes are watched. Google Consent Mode pushes an arguments object rather than a plain one; Jarhead handles both. Confirm the value is actually granted:

window.dataLayer.filter(e => JSON.stringify(e).includes('analytics_storage'))

Event mode. The event is listened for on both window and document. Fire it manually to prove the wiring:

window.dispatchEvent(new Event('jarhead:consent'));

Manual mode. Nothing opens the gate but window.jarhead.consent().

A gate that can never open — cookie mode with no cookie name — is reported as an error on the settings screen and in Utilities → Hotjar, not left for you to discover.

Nothing loads and the console shows a CSP error

A Content Security Policy is the most common reason a correctly installed tracking code does nothing, because the browser reports the block somewhere nobody is looking. Utilities → Hotjar prints the exact directives; see Configuration.

If you cannot allow 'unsafe-inline' on script-src, set scriptNonce to whatever generates the nonce for the request. Jarhead puts it on its own script tag and on the Hotjar script that tag creates.

Events fired from a template never arrive

They are queued until the gate opens, then flushed in order — that is by design. Check the queue:

window.jarhead.status().queued

A non-zero queued with booted: false means the events are fine and the gate is the problem.

Every session count is double

Something is placing the tracking code twice. Either a template calls {{ craft.jarhead.snippet }} and something else adds Hotjar independently — a tag manager, a hard-coded snippet in a layout, another plugin — or Hotjar's own installer was left in place.

Jarhead itself cannot double up: calling the Twig tag stands automatic injection down for that page, and the browser gate refuses to run twice.

Search your templates for static.hotjar.com and check your tag manager.

The page is truncated at the point the snippet was added

That is a stale content-length header, and Jarhead restamps it — so if you are seeing this, something after Jarhead is modifying the response body without restamping. Look at any other plugin or module hooking Response::EVENT_AFTER_PREPARE.

Recordings are full of your own team

Turn on the exclusions. excludeAdmins is on by default, but excludeLoggedIn and excludedGroups are not:

'excludeLoggedIn' => true,
'excludedGroups'  => ['editors', 'staff'],

Then confirm with the impersonation the tester gives you:

php craft jarhead/explain blog/hello --user=14