Content Analytics SDK for tracking content-aware events in dotCMS-powered React applications.
dotCMS SDKs are published in lockstep with dotCMS itself: every @dotcms/* package ships
at the exact same version number as the dotCMS release it was built for (e.g. dotCMS
26.7.14-1 → @dotcms/client@26.7.14-1, @dotcms/react@26.7.14-1, and so on).
Simple rule of thumb: use the SDK version that matches your dotCMS instance's version.
You don't have to upgrade the SDK every time dotCMS releases a new version (or vice versa). Most releases don't change anything the SDKs rely on, so an older SDK usually keeps working fine against a newer dotCMS instance. Occasionally, though, a release does include a real breaking change — and if your SDK is older than that point, it will stop working correctly.
You don't need to track this yourself: your dotCMS instance always knows the oldest SDK version it still supports, and the SDK checks itself against it automatically. If you're using an SDK that's too old, you'll see a clear warning in your console telling you to upgrade.
Recommendation: pin your SDKs to the same version as your dotCMS instance, and only bump them when you upgrade dotCMS — or when the console tells you to.
On an LTS release? LTS releases don't currently get their own matching SDK version. Until that's addressed, use the SDK version published for the closest regular release at or before your LTS version.
Want more background on how dotCMS releases and support windows work? See Release & Support Lifecycle.
npm install @dotcms/analytics// src/config/analytics.config.js
export const analyticsConfig = {
siteAuth: process.env.NEXT_PUBLIC_DOTCMS_ANALYTICS_SITE_KEY,
server: process.env.NEXT_PUBLIC_DOTCMS_ANALYTICS_HOST,
autoPageView: true,
debug: process.env.NEXT_PUBLIC_DOTCMS_ANALYTICS_DEBUG === 'true',
impressions: true,
clicks: true
};// src/app/layout.js
import { DotContentAnalytics } from '@dotcms/analytics/react';
import { analyticsConfig } from '@/config/analytics.config';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<DotContentAnalytics config={analyticsConfig} />
{children}
</body>
</html>
);
}'use client';
import { useContentAnalytics } from '@dotcms/analytics/react';
import { analyticsConfig } from '@/config/analytics.config';
function ContactForm() {
const { conversion } = useContentAnalytics(analyticsConfig);
const handleSubmit = (e) => {
e.preventDefault();
// ... submit form logic ...
// Track conversion ONLY after successful submission
conversion('form-submit', {
formName: 'contact-us',
formType: 'lead-gen'
});
};
return <form onSubmit={handleSubmit}>{/* form fields */}</form>;
}The SDK exports two React primitives. Understanding their roles is critical for correct usage.
<DotContentAnalytics /> -- Automatic Page View Tracker#useContentAnalytics(config) -- Manual Tracking Hook#config as a parameter -- it does not read from context// Every component that tracks events must import config explicitly
import { useContentAnalytics } from '@dotcms/analytics/react';
import { analyticsConfig } from '@/config/analytics.config';
const { track, pageView, conversion } = useContentAnalytics(analyticsConfig);Why centralize config? You must import it in each component, but having a single file prevents duplication and makes updates easier.
Add these to your .env.local file:
NEXT_PUBLIC_DOTCMS_ANALYTICS_SITE_KEY=YOUR_ANALYTICS_SITE_KEY
NEXT_PUBLIC_DOTCMS_ANALYTICS_HOST=http://localhost:8080
NEXT_PUBLIC_DOTCMS_ANALYTICS_DEBUG=true| Variable | Description |
|---|---|
NEXT_PUBLIC_DOTCMS_ANALYTICS_SITE_KEY | Site auth key from the Content Analytics app in dotCMS |
NEXT_PUBLIC_DOTCMS_ANALYTICS_HOST | URL where your dotCMS instance is running |
NEXT_PUBLIC_DOTCMS_ANALYTICS_DEBUG | Set to "true" to enable verbose console logging |
| Option | Type | Required | Default | Description |
|---|---|---|---|---|
siteAuth | string | Yes | -- | Site auth from dotCMS Analytics app |
server | string | Yes | -- | Your dotCMS server URL |
debug | boolean | No | false | Enable verbose logging |
autoPageView | boolean | No | true | Auto track page views on route changes |
queue | QueueConfig | false | No | See below | Event batching configuration |
impressions | ImpressionConfig | boolean | No | false | Content impression tracking |
clicks | boolean | No | false | Content click tracking (300ms throttle) |
Controls how events are batched before being sent to the server:
false: Disable queuing, send events immediatelyundefined (default): Enable queuing with default settingsQueueConfig object: Custom settings| Option | Type | Default | Description |
|---|---|---|---|
eventBatchSize | number | 15 | Max events per batch -- auto-sends when reached |
flushInterval | number | 5000 | Time between flushes in milliseconds |
How it works:
eventBatchSize is reachedflushInterval millisecondsvisibilitychange + pagehidenavigator.sendBeacon() for reliable delivery on page unload// Disable queuing (send immediately)
export const analyticsConfig = {
siteAuth: process.env.NEXT_PUBLIC_DOTCMS_ANALYTICS_SITE_KEY,
server: process.env.NEXT_PUBLIC_DOTCMS_ANALYTICS_HOST,
queue: false
};
// Custom queue settings
export const analyticsConfig = {
siteAuth: process.env.NEXT_PUBLIC_DOTCMS_ANALYTICS_SITE_KEY,
server: process.env.NEXT_PUBLIC_DOTCMS_ANALYTICS_HOST,
queue: {
eventBatchSize: 10,
flushInterval: 3000
}
};Controls automatic tracking of content visibility in the viewport:
false or undefined (default): Disabledtrue: Enabled with default settingsImpressionConfig object: Custom settings| Option | Type | Default | Description |
|---|---|---|---|
visibilityThreshold | number | 0.5 | Min percentage visible (0.0 to 1.0) |
dwellMs | number | 750 | Min time visible in milliseconds |
maxNodes | number | 1000 | Max elements to track (performance limit) |
How it works:
dotcms-contentlet class and data-dot-* attributes// Enable with defaults (50% visible, 750ms dwell)
export const analyticsConfig = {
// ...required fields
impressions: true
};
// Custom thresholds
export const analyticsConfig = {
// ...required fields
impressions: {
visibilityThreshold: 0.7,
dwellMs: 1000,
maxNodes: 500
}
};Controls automatic tracking of user clicks on content elements:
false or undefined (default): Disabledtrue: Enabled with 300ms throttleHow it works:
<a> and <button> elements within contentletsdotcms-contentlet class and data-dot-* attributeshref, aria-label, data-*) and excludes CSS classesCaptured data per click:
identifier, inode, title, content_typetext, type (a/button), id, class, href, attributesviewport_offset_pct, dom_indexYou can enrich click data using data-* attributes in your HTML:
<a
href="/signup"
id="cta-signup"
data-category="primary-cta"
data-campaign="summer-sale"
aria-label="Sign up for free trial">
Start Free Trial
</a>
<button data-action="download" data-file-type="pdf" data-category="lead-magnet">
Download Whitepaper
</button>The SDK sends four types of events, identified by event_type:
| Event Type | Trigger | Requires |
|---|---|---|
pageview | Automatically on route change, or manually via pageView() | autoPageView: true or manual call |
content_impression | When a contentlet becomes visible in the viewport | impressions config enabled |
content_click | When a user clicks a link/button inside a contentlet | clicks config enabled |
conversion | Explicitly via conversion() after a successful business action | Manual call |
The pageView() method tracks page navigation events. It automatically enriches the event with:
You can optionally pass custom data that will be sent in addition to all the automatic enrichment.
The conversion() method tracks user conversions (purchases, downloads, sign-ups, etc.).
Only track conversions after a successful action or completed goal. Tracking on clicks or attempts (before success) diminishes their value as conversion metrics. Track when:
The track() method tracks any custom user action with a unique event name and optional properties.
eventName cannot be "pageview" or "conversion" (reserved)"button-click", "form-submit", "video-play", etc.dot_analytics_user_idAdd DotContentAnalytics to your root layout. No additional code is needed -- page views are tracked on every route change.
// src/app/layout.js
import { DotContentAnalytics } from '@dotcms/analytics/react';
import { analyticsConfig } from '@/config/analytics.config';
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
<DotContentAnalytics config={analyticsConfig} />
{children}
</body>
</html>
);
}'use client';
import { useEffect } from 'react';
import { useContentAnalytics } from '@dotcms/analytics/react';
import { analyticsConfig } from '@/config/analytics.config';
function BlogPost({ post }) {
const { pageView } = useContentAnalytics(analyticsConfig);
useEffect(() => {
pageView({
contentType: 'blog',
category: post.category,
author: post.author,
wordCount: post.wordCount
});
}, []);
return <article>{/* post content */}</article>;
}'use client';
import { useContentAnalytics } from '@dotcms/analytics/react';
import { analyticsConfig } from '@/config/analytics.config';
function CallToAction() {
const { track } = useContentAnalytics(analyticsConfig);
const handleClick = () => {
track('cta-click', {
button: 'Buy Now',
location: 'hero-section',
price: 299.99
});
};
return <button onClick={handleClick}>Buy Now</button>;
}This example is based on the Contact Us form in the Next.js example app:
'use client';
import { useState } from 'react';
import { useContentAnalytics } from '@dotcms/analytics/react';
import { analyticsConfig } from '@/config/analytics.config';
export default function ContactUs({ description }) {
const [isSubmitting, setIsSubmitting] = useState(false);
const [isSuccess, setIsSuccess] = useState(false);
const { conversion } = useContentAnalytics(analyticsConfig);
const handleSubmit = (e) => {
e.preventDefault();
setIsSubmitting(true);
// Simulate form submission
setTimeout(() => {
setIsSuccess(true);
// Track conversion ONLY after successful submission
conversion('form-submit', {
formName: 'contact-us',
formType: 'lead-gen'
});
}, 3000);
};
return (
<form onSubmit={handleSubmit}>
{/* form fields */}
<button type="submit" disabled={isSubmitting}>
{isSubmitting ? 'Submitting...' : 'Submit'}
</button>
</form>
);
}'use client';
import { useContentAnalytics } from '@dotcms/analytics/react';
import { analyticsConfig } from '@/config/analytics.config';
function CheckoutButton({ product, quantity }) {
const { conversion } = useContentAnalytics(analyticsConfig);
const handlePurchase = async () => {
// Process payment...
const result = await processPayment(product, quantity);
if (result.success) {
// Track conversion ONLY after confirmed payment
conversion('purchase', {
value: product.price * quantity,
currency: 'USD',
productId: product.sku,
category: product.category
});
}
};
return <button onClick={handlePurchase}>Complete Purchase</button>;
}<DotContentAnalytics />#React component for automatic page view tracking on route changes.
interface DotContentAnalyticsProps {
config: DotCMSAnalyticsConfig;
}Place once in your root layout. Wraps the internal tracker in <Suspense> for Next.js App Router compatibility.
useContentAnalytics(config)#React hook that returns tracking methods. Always requires config as a parameter.
function useContentAnalytics(config: DotCMSAnalyticsConfig): DotCMSAnalytics;
interface DotCMSAnalytics {
/** Track a page view with optional custom data */
pageView: (customData?: Record<string, unknown>) => void;
/** Track a custom event (eventName cannot be "pageview" or "conversion") */
track: (eventName: string, properties?: Record<string, unknown>) => void;
/** Track a conversion after a successful business action */
conversion: (name: string, options?: Record<string, unknown>) => void;
}DotCMSAnalyticsConfig#interface DotCMSAnalyticsConfig {
server: string;
siteAuth: string;
debug?: boolean;
autoPageView?: boolean;
queue?: QueueConfig | false;
impressions?: ImpressionConfig | boolean;
clicks?: boolean;
}
interface QueueConfig {
eventBatchSize?: number;
flushInterval?: number;
}
interface ImpressionConfig {
visibilityThreshold?: number;
dwellMs?: number;
maxNodes?: number;
}When you call pageView(customData?), the SDK sends:
{
context: {
site_key: string; // Your site key
session_id: string; // Current session ID
user_id: string; // Anonymous user ID
device: {
screen_resolution: string;
language: string;
viewport_width: string;
viewport_height: string;
}
},
events: [{
event_type: "pageview",
local_time: string, // ISO 8601 timestamp with timezone
data: {
page: { // Captured automatically
url: string;
title: string;
referrer: string;
path: string;
doc_host: string;
doc_protocol: string;
doc_search: string;
doc_hash: string;
doc_encoding: string;
},
utm?: { // Captured automatically (if present in URL)
source: string;
medium: string;
campaign: string;
term: string;
content: string;
},
custom?: { // Your optional data from pageView(customData)
// Any properties you pass
}
}
}]
}When you call track(eventName, properties):
{
context: { /* same as above */ },
events: [{
event_type: string, // Your custom event name
local_time: string,
data: {
custom: {
// Your properties object
}
}
}]
}When you call conversion(name, options):
{
context: { /* same as above */ },
events: [{
event_type: "conversion",
local_time: string,
data: {
conversion: {
name: string; // Your conversion name
},
page: {
url: string;
title: string;
},
custom?: { // Your optional data from options parameter
// All properties from options
}
}
}]
}When click tracking is enabled and a user clicks on a contentlet element:
{
"content": {
"identifier": "abc123",
"inode": "xyz789",
"title": "Product Page",
"content_type": "Page"
},
"element": {
"text": "Start Free Trial",
"type": "a",
"id": "cta-signup",
"class": "btn btn-primary",
"href": "/signup",
"attributes": [
"data-category:primary-cta",
"data-campaign:summer-sale",
"aria-label:Sign up for free trial"
]
},
"position": {
"viewport_offset_pct": 45.2,
"dom_index": 2
}
}| Key | Purpose |
|---|---|
dot_analytics_user_id | Anonymous user identifier (persisted across sessions) |
dot_analytics_session_id | Current session ID |
dot_analytics_session_utm | UTM campaign data for the session |
dot_analytics_session_start | Session start timestamp |
Analytics are automatically disabled when inside the dotCMS Universal Visual Editor (UVE). No events are sent in editor mode.
All events are sent via POST to:
{server}/api/v1/analytics/content/eventWhere {server} is the server value from your config.
Set debug: true in your config to see verbose logging in the browser console.
/api/v1/analytics/content/eventOpen browser DevTools > Application > Local Storage and look for:
dot_analytics_user_iddot_analytics_session_iddot_analytics_session_utmdot_analytics_session_startEvents not appearing?
siteAuth and server are correct in your configdebug: true to see console logs.env.local (restart dev server after changes)NEXT_PUBLIC_Queue not flushing?
eventBatchSize -- the threshold might not be reached yetflushInterval is appropriate for your use casevisibilitychangeSession not persisting?
We offer multiple channels to get help with the dotCMS Analytics SDK:
dotcms-analytics when posting questions.When reporting issues, please include:
GitHub pull requests are the preferred method to contribute code to dotCMS. We welcome contributions to the dotCMS Analytics SDK! If you'd like to contribute, please follow these steps:
git checkout -b feature/amazing-feature)git commit -m 'Add some amazing feature')git push origin feature/amazing-feature)Please ensure your code follows the existing style and includes appropriate tests.
dotCMS is available under either the Business Source License 1.1 (BSL) or a commercial license.
Under the BSL, dotCMS can be used at no cost by individual developers, small businesses or agencies under $5M in total finances, and by larger organizations in non-production environments. Every BSL release automatically converts to GPL v3 four years after its release date. For full terms and FAQs, visit dotcms.com/bsl and dotcms.com/bsl-faq.
Production use in larger organizations, along with access to managed cloud, SLAs, support, and enterprise capabilities, is available under a commercial license from dotCMS. For details on commercial plans, features, and support options, see dotcms.com/pricing.
latestFound an issue with this documentation? View the source