Setup: Traditional
This guide covers sites whose pages are rendered by dotCMS templates and layouts. If your front end is a separate application, see Headless setup instead.
Add the Tracking Call#
Analytics tracking is added by calling $!dotAnalytics.code() in your Velocity template, at the exact location where the <script> tag should appear — normally inside <head>. dotCMS builds the full script tag from your Content Analytics app configuration; you control where it lands.
<head>
…
$!dotAnalytics.code()
</head>Put it in the theme
The theme is shared across all pages, so a single change gives consistent coverage. If your site uses multiple themes, custom templates, or layouts, work with whoever manages them to identify every place that needs the call.
Every page you want tracked must include the call
This is an explicit opt-in. A template that does not call $!dotAnalytics.code() produces no tracking data at all for its pages — and they will look as though they were never visited.
That covers the content on those pages too: no clicks, no impressions, and no conversions from anything they render. And because content metrics add up across every page an item appears on, a missed template does more than drop those pages from your reports — it quietly understates the content itself everywhere else it appears, and skews its conversion attribution. A single missed template is a silent gap, not a visible one.
What the Call Returns#
- On a LIVE page with the Content Analytics app configured for the site → the full analytics
<script>tag. - In preview or edit mode, when no app is configured, or when analytics is disabled → an empty string. The
$!silent syntax ensures nothing is printed.
Do not use automatic injection
dotCMS can also inject the script automatically via the FEATURE_FLAG_CONTENT_ANALYTICS_AUTO_INJECT feature flag. This is not the recommended path, for three reasons:
- It gives template authors no control over placement — the script is always injected globally.
- It injects unconditionally, including on pages that return JSON, XML or plain text, appending the tag to the response body and breaking any parser consuming it.
- It does not switch itself off. Using
$!dotAnalytics.code()while the flag is on produces double injection — you must turn the flag off yourself.
Auto-injection is controlled by an instance-level feature flag. If it is enabled on your environment, contact dotCMS Support to have it turned off before you adopt the ViewTool.
Version requirement
$!dotAnalytics.code() is available from dotCMS 26.07.06-02 onwards.
What dotCMS Generates#
You never write this tag by hand. It is shown so you know what to expect on the page:
<script src="/ext/analytics/ca.min.js"
data-analytics-auth="YOUR_SITE_AUTH"
data-analytics-debug="false"
data-analytics-auto-page-view="true"
data-analytics-impressions="false"
data-analytics-clicks="false"
data-analytics-config='{…}'>
</script>Configuration Options#
What the script does once it is on the page is not set in your template — it is set in the Content Analytics app, under Server-Rendered Pages Configuration, on the site you are configuring. The ViewTool decides where the script goes; these settings decide how it behaves, and they are what gets written into the tag shown above.
These settings are for server-rendered pages only
They have no effect on a headless application, which configures the same behaviour in its own code. A site serving both — some pages from dotCMS templates, some from your own app — needs each surface set up on its own terms.
| Setting | What it does | Default |
|---|---|---|
Enable Auto Page Views autoPageView | Track a page view on every page load. | true |
Enable Content Impressions contentImpression | Track when content items become visible in the viewport. | false |
Enable Content Clicks contentClick | Track clicks on links and buttons inside content items. | false |
Enable Debug Mode debug | Log analytics activity to the browser console. | false |
Advanced Configuration#
The four toggles cover what most sites need. For finer control — how events are batched, and how strict the impression threshold is — the same section of the app has an Advanced Configuration field that takes JSON. Whatever you put there is serialized into the data-analytics-config attribute of the generated script.
{
"queue": { "eventBatchSize": 15, "flushInterval": 5000 },
"impressions": { "visibilityThreshold": 0.5, "dwellMs": 750, "maxNodes": 100 }
}| Option | Meaning | Default |
|---|---|---|
queue.eventBatchSize | Send the batch once this many events are queued. | 15 |
queue.flushInterval | Send queued events at least this often (ms). | 5000 |
impressions.visibilityThreshold | Fraction of the element that must be visible (0–1). | 0.5 |
impressions.dwellMs | How long it must stay visible to count (ms). | 750 |
impressions.maxNodes | Maximum content items observed for impressions on a page. | 100 |
Applying changes — clear the cache
After changing any Content Analytics setting, go to Settings → Maintenance → Cache and flush the Velocity (Velocity2) cache. Already-cached pages keep the previous configuration until you do — this fails silently, producing skewed data rather than an error.
Making Content Trackable#
You do not add any markup
Turning on Content Impressions or Content Clicks is all it takes. From then on, dotCMS wraps every content item rendered through a container with the attributes the tracker needs. Your templates and containers stay exactly as they are.
On a published page, a rendered content item comes out looking like this. You never write it — it is shown so you can recognise it when inspecting a page:
<div class="dotcms-contentlet"
data-dot-identifier="8a7d5e1f…"
data-dot-inode="3c2b9f04…"
data-dot-title="Spring Sale Hero"
data-dot-type="Banner"
data-dot-basetype="CONTENT">
<a href="/signup" data-category="primary-cta">Start Free Trial</a>
<button data-action="subscribe">Subscribe</button>
</div>Inside that wrapper, only <a> and <button> elements are tracked for clicks. Any data-* attributes you put on them — like the two above — are captured with the event, which is the one place your own markup makes a difference.
Content rendered outside a container is not wrapped
The wrapper is added as containers render their content. Content you pull directly in Velocity — a custom query in a template or widget, for example — bypasses that path and is not tracked, even with the toggles on. To track it, add the class and the data-dot-* attributes above yourself.
Shadow DOM and client-side rendering
Automatic click and impression detection relies on this markup being present in the static DOM. For content rendered via Shadow DOM (for example Stencil web components) or injected by a client-side JavaScript library, automatic detection is not guaranteed and has not been fully validated. Page view and conversion tracking are unaffected. If your content renders this way, validate tracking on your pages or send those interactions explicitly with window.dotAnalytics.track(…).