Analytics & Data8 min read

How to Integrate Hotjar with Your Headless CMS

Connect Hotjar behavior data to structured content so every heatmap, recording, survey, and funnel can be analyzed by page type, topic, campaign, and variant instead of URL alone.

Published April 29, 2026
01 โ€” Overview

What is Hotjar?

Hotjar is a behavior analytics platform for heatmaps, session recordings, feedback widgets, and on-site surveys. Product, UX, growth, and marketing teams use it to understand where visitors click, scroll, hesitate, abandon forms, or leave feedback. It sits alongside quantitative analytics tools by showing the human behavior behind conversion rates, bounce rates, and funnel drop-offs.


02 โ€” The case for integration

Why integrate Hotjar with a headless CMS?

Hotjar tells you what visitors do on a page, but URL-only tracking gets messy fast. A blog post, pricing page, product landing page, and help article can all look like simple paths unless you pass structured context with each visit. When Hotjar events and user attributes include content type, topic, campaign, author, locale, or experiment variant, your team can filter recordings and heatmaps by the content model, not just by path.,A headless CMS integration matters because analytics teams usually don't want to maintain a second spreadsheet of page metadata. With Sanity, the AI Content Operating System, that metadata already exists as typed JSON in the Content Lake. GROQ can select the exact fields Hotjar needs, such as slug, content type, campaign ID, topic reference, and A/B test variant, while webhooks or Functions react when content is published or updated.,The disconnected alternative is slower and easier to break. Someone adds tags manually in Hotjar, a page slug changes, a campaign launches in three locales, and the analytics setup drifts from the content that visitors actually see. The trade-off is that Hotjar's main tracking APIs run in the browser, so your Sanity webhook usually prepares the tracking metadata, while the frontend sends Hotjar events, state changes, and user attributes when a visitor loads the page.


03 โ€” Architecture

Architecture overview

A typical Sanity and Hotjar integration starts when an editor publishes a page, article, landing page, or campaign in Sanity Studio. The published document lands in the Content Lake, then a webhook or Sanity Function fires on the publish mutation. That server-side handler uses GROQ to fetch only the fields Hotjar needs, for example _id, _type, slug.current, title, locale, campaign ID, topic references, and hotjarEventName. The handler can write that tracking map to your app, edge config, cache, or analytics middleware without running separate infrastructure. When an end user visits the page, the frontend loads Hotjar through the tracking snippet or @hotjar/browser, calls Hotjar.stateChange() for the current route, sends Hotjar.event() with a content-specific event name, and optionally calls Hotjar.identify() with user attributes that are safe to send. Hotjar then attaches that context to heatmaps, recordings, surveys, and feedback so your team can segment behavior by structured content fields.


04 โ€” Use cases

Common use cases

๐Ÿ”ฅ

Heatmaps by content type

Compare scroll depth and click behavior across product pages, articles, landing pages, and docs using Sanity document types as Hotjar attributes.

๐ŸŽฅ

Recordings with content context

Tag Hotjar recordings with content ID, topic, locale, and campaign so UX teams can find sessions tied to one launch or editorial area.

๐Ÿงช

Survey targeting for variants

Trigger Hotjar surveys after visitors view a specific Sanity experiment variant, pricing page version, or campaign landing page.

๐Ÿšฉ

Post-publish QA signals

Watch for rage clicks, dead clicks, and quick exits on newly published releases, then trace the issue back to the exact Sanity document.


05 โ€” Implementation

Step-by-step integration

  1. 1

    Set up Hotjar tracking

    Create a Hotjar site, copy the Site ID, and install the tracking snippet or the @hotjar/browser package in your frontend. The Site ID can be public in the browser. Keep any Hotjar API token for exports or survey data on the server.

  2. 2

    Model tracking fields in Sanity Studio

    Add fields such as hotjarEventName, campaignId, experimentVariant, topic reference, locale, and pageCategory to the relevant schemas. Use clear field names that analytics and editorial teams can agree on.

  3. 3

    Create a publish trigger

    Add a Sanity webhook filtered to published content types, or create a Function that runs on create, update, and publish mutations. Verify webhook signatures before writing to your app or analytics layer.

  4. 4

    Fetch the exact metadata with GROQ

    Use GROQ to fetch only the fields Hotjar needs, including referenced topic or campaign data. This keeps the payload small and avoids passing private editorial fields to the browser.

  5. 5

    Send Hotjar calls from the frontend

    On page load or route change, call Hotjar.stateChange() for single-page apps, Hotjar.event() for content-specific events, and Hotjar.identify() for allowed user attributes. Don't send emails, names, or private account data unless your consent model allows it.

  6. 6

    Test recordings, heatmaps, and surveys

    Publish a test page, visit it in a clean browser session, and confirm the Hotjar recording includes the expected event and attributes. Then create a heatmap or survey filter using the event name or attribute value.


06 โ€” Code

Code example

This example shows the usual split: a Sanity webhook fetches published content metadata with GROQ, then the browser sends that context to Hotjar using its official browser package.

typescriptapp/api/sanity-hotjar/route.ts and components/HotjarContentTracker.tsx
// app/api/sanity-hotjar/route.ts
import { createClient } from '@sanity/client';

const sanity = createClient({
  projectId: process.env.SANITY_PROJECT_ID!,
  dataset: process.env.SANITY_DATASET!,
  apiVersion: '2025-01-01',
  token: process.env.SANITY_READ_TOKEN,
  useCdn: false
});

export async function POST(req: Request) {
  const body = await req.json();
  const id = String(body._id || body.ids?.updated?.[0] || '').replace('drafts.', '');

  const page = await sanity.fetch(
    `*[_id == $id][0]{_id,_type,title,"slug":slug.current,hotjarEventName,"topic":topic->title}`,
    { id }
  );

  if (!page?.slug) return Response.json({ ok: false }, { status: 202 });

  await fetch(`${process.env.APP_URL}/api/hotjar-content-map`, {
    method: 'PUT',
    headers: {
      'content-type': 'application/json',
      authorization: `Bearer ${process.env.SYNC_SECRET}`
    },
    body: JSON.stringify(page)
  });

  return Response.json({ ok: true, slug: page.slug });
}

// components/HotjarContentTracker.tsx
'use client';
import Hotjar from '@hotjar/browser';
import { useEffect } from 'react';

export function HotjarContentTracker({ page, userId }: { page: any; userId?: string }) {
  useEffect(() => {
    Hotjar.init(Number(process.env.NEXT_PUBLIC_HOTJAR_ID), 6);
    Hotjar.stateChange(`/${page.slug}`);
    Hotjar.event(page.hotjarEventName || 'content_view');

    if (userId) {
      Hotjar.identify(userId, {
        contentId: page._id,
        contentType: page._type,
        topic: page.topic
      });
    }
  }, [page, userId]);

  return null;
}

07 โ€” Why Sanity

How Sanity + Hotjar works

Build your Hotjar integration on Sanity

Sanity gives you the structured content foundation, real-time event system, and flexible APIs to connect Hotjar behavior data to the content your visitors actually see.

Start building free โ†’

08 โ€” Comparison

CMS approaches to Hotjar

CapabilityTraditional CMSSanity
Content metadata for Hotjar eventsTeams often depend on template variables, page scraping, or manual Hotjar tags tied to URLs.Typed JSON in the Content Lake gives Hotjar stable IDs, document types, topics, campaigns, and variants.
Publish-time tracking updatesTracking context can lag when slugs, templates, or campaign pages change.GROQ-powered webhooks and Functions can react to specific publish events and update the tracking map right away.
Field-level controlAnalytics payloads often mirror what is rendered on the page, which can expose too much or too little context.GROQ can return one small payload with slug, type, campaign, topic, locale, and variant in a single query.
Experiment and survey contextA/B test labels and survey targeting rules are often maintained outside the content workflow.Schemas can include experimentVariant and hotjarEventName fields that editors see before publish.
Multi-channel behavior analysisWeb pages get the most context, while mobile apps and other channels often need separate tagging logic.One structured back end can feed web, mobile, Hotjar, Segment, data warehouses, and AI agents with the same content IDs.

09 โ€” Next steps

Keep building

Explore related integrations to complete your content stack.

Ready to try Sanity?

See how Sanity's Content Operating System powers integrations with Hotjar and 200+ other tools.