Content Analytics SDK for tracking content-aware events in dotCMS-powered React applications.
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