Medallia Digital Experience Analytics (DXA) from GTM
Implementing Medallia Digital Experience Analytics (DXA) in GTM — A Sandboxed JavaScript Recipe
Medallia Digital Experience Analytics — DXA, formerly known as Decibel Insight — ships its standard
installation instructions as a Custom HTML snippet: a pair of dns-prefetch links followed
by an inline <script> block that builds a queue object on window and
injects the Decibel loader. That works, but every Custom HTML tag with inline JavaScript is a Content
Security Policy liability, and a Custom HTML tag has no concept of consent state once the page has
loaded. Hardcoded client and instance IDs make it painful to route production and development traffic
to different DXA instances.
I would prefer to deploy as little custom JavaScript as possible to achieve the fastest, cleanest web
measurement implementation possible. This post walks through the GTM Sandboxed JavaScript template I
built to deploy DXA, the companion template that makes DXA "blind and mute" when consent is denied, and
the full GTM Tag Template (.tpl) source — parameters, permissions, and all — that ties it
together.
The Standard Decibel Insight Installation Script
Medallia's own documentation recommends installing DXA with the following markup: two
dns-prefetch hints to warm the connection to Medallia's CDN, followed by an inline script
that defines an error-tracking array, a command queue, and asynchronously injects the Decibel loader
script.
<link rel="dns-prefetch" href="//cdn.decibelinsight.net">
<link rel="dns-prefetch" href="//collection.decibelinsight.net">
<script type="text/javascript">
// <![CDATA[
(function(d,e,c,i,b,el,it) {
d._da_=d._da_||[];_da_.oldErr=d.onerror;_da_.err=[];
d.onerror=function(){_da_.err.push(arguments);_da_.oldErr&&_da_.oldErr.apply(d,Array.prototype.slice.call(arguments));};
d.DecibelInsight=b;d[b]=d[b]||function(){(d[b].q=d[b].q||[]).push(arguments);};
el=e.createElement(c),it=e.getElementsByTagName(c)[0];el.async=1;el.src=i;it.parentNode.insertBefore(el,it);
})(window,document,'script','https://cdn.decibelinsight.net/i/99999/1111111/di.js','decibelInsight');
// ]]>
</script>
This is the snippet a Custom HTML tag would need to reproduce verbatim, IDs and all. It works, but it means a new SHA-256 hash in your CSP every time the script changes, and the client ID / instance ID (the two integers embedded in the loader URL) are hardcoded directly into markup rather than driven by GTM variables.
Why Build a Sandboxed JavaScript Template Instead
This may look like a lot of additional structure for what is functionally the same outcome, but it is
just doing the above with a Sandboxed JavaScript Custom Template instead of a Custom HTML tag. The
sandboxed template runs inside GTM's own permission-gated runtime rather than injecting a raw
<script> block into the DOM, which means:
- The client ID, instance ID, and CDN prefix become typed template parameters instead of hardcoded string literals, so the same template can be reused across properties and environments.
- GTM's
injectScriptAPI requests permission for a specific URL allow-list (access_globals,inject_script) rather than relying on broadunsafe-inlineCSP allowances. - The template's success/failure callbacks (
data.gtmOnSuccess/data.gtmOnFailure) let downstream tags sequence off of DXA actually loading, which a fire-and-forget Custom HTML tag cannot do.
const log = require('logToConsole');
const injectScript = require('injectScript');
const createQueue = require('createQueue');
const setInWindow = require('setInWindow');
const copyFromWindow = require('copyFromWindow');
// Get template parameters
const decibelClientId = data.decibelClientId;
const decibelInstanceId = data.decibelInstanceId;
const decibelScriptPrefix = data.decibelScriptPrefix;
// Build script URL
const decibelScriptUrl = decibelScriptPrefix + decibelClientId + '/' + decibelInstanceId + '/di.js';
// Initialize Decibel Insight
const initializeDecibelInsight = () => {
// Set up error tracking array
const copyFromWindow = require('copyFromWindow');
const existingDa = copyFromWindow('_da_');
if (!existingDa) {
setInWindow('_da_', []);
setInWindow('_da_.err', []);
}
// Create DecibelInsight queue
const decibelInsightQueue = createQueue('decibelInsight');
setInWindow('DecibelInsight', 'decibelInsight');
log('Medallia DXA: Initialized DecibelInsight queue');
};
// Initialize the tracking
initializeDecibelInsight();
// Inject the Decibel Insight script
injectScript(decibelScriptUrl, () => {
log('Medallia DXA: Script loaded successfully');
data.gtmOnSuccess();
}, () => {
log('Medallia DXA: Failed to load script');
data.gtmOnFailure();
}, 'decibelInsight');
The trigger for this tag is the "regular" initialization trigger — the one that fires after your consent initialization trigger, not before it. DXA should never load ahead of your consent gate.
Template Parameters
The published GTM Tag Template wraps the script above with three typed, validated parameters so the template can be reused without editing code per-property:
| Parameter | Type | Validation | Notes |
|---|---|---|---|
decibelClientId | Text | Non-empty, positive number | The 1st integer in the DXA loader URL. |
decibelInstanceId | Text | Non-empty, positive number | The 2nd integer in the DXA loader URL. |
decibelScriptPrefix | Text | Non-empty, regex ^https\:\/\/.*decibel.*\/$ | Defaults to https://cdn.decibelinsight.net/i/; the regex keeps the field constrained to a Decibel/Medallia CDN host. |
Now that the template takes parameters, you can drive decibelInstanceId from a Lookup
Table keyed on hostname (or any other criteria) if you want to vary the DXA instance for production and
development without maintaining two separate tags.
Required Web Permissions
Because the template runs inside GTM's sandbox, it has to explicitly request every capability it
uses. These are the permissions declared in the .tpl file's
___WEB_PERMISSIONS___ block:
| API | Scope granted |
|---|---|
logging | Console logging restricted to the debug environment only. |
inject_script | Permission to inject a <script> element whose source begins with https://cdn.decibelinsight.net/. |
access_globals | Read / write / execute access to four specific window keys: _da_, _da_.err, DecibelInsight, and decibelInsight. |
That last permission is the important one for the consent-blocking template below: GTM will refuse
to let a sandboxed template touch window.decibelInsight at all unless
access_globals explicitly lists it with read/write/execute access.
Consent Compliance: Making DXA Blind and Mute
But what if you have a React JS single-page application? For that matter, what if consent is denied after DXA has already loaded and is mid-session, rather than before the next page load? How do you keep a JavaScript API like this consent-compliant when it is designed to keep running once injected?
That requires a second GTM Sandboxed JavaScript template. This script doesn't try to stop DXA from existing — it neuters it in place, using multiple techniques at once: replacing its public tracking methods with no-ops, flipping its internal opt-out flag, and finally nulling the global reference itself.
const createQueue = require('createQueue');
const setInWindow = require('setInWindow');
const copyFromWindow = require('copyFromWindow');
const decibelInsight = copyFromWindow('decibelInsight');
const decibelInsight_initiated = copyFromWindow('decibelInsight_initiated');
if (typeof decibelInsight !== 'undefined' && typeof decibelInsight_initiated !== 'undefined' && decibelInsight_initiated && decibelInsight) {
setInWindow('decibelInsight.track', function() {}, true);
setInWindow('decibelInsight.sendEvent', function() {}, true);
setInWindow('decibelInsight.optOut', true, true);
setInWindow('decibelInsight', null, true);
}
// Call data.gtmOnSuccess when the tag is finished
data.gtmOnSuccess();
This tag is triggered without parameters, based on the event that fires when a visitor clicks to deny consent. That click handler should additionally check the status of your relevant consent category for "denied" before this tag is allowed to run. How you wire that condition depends heavily on which consent management platform you're running.
Re-enabling DXA after a visitor later grants consent isn't a matter of "undoing" this script — once
decibelInsight has been nulled out, the only path back is to reinitialize from scratch using
the first template above.
What's on GitHub
The base initialization template — parameters, permissions, and sandboxed JavaScript exactly as shown
above — is published as an importable GTM Tag Template .tpl file on GitHub:
| File | Contents |
|---|---|
Medallia Digital Experience Analytics (DXA).tpl | The full GTM Custom Template definition: metadata/brand block, the three decibelClientId / decibelInstanceId / decibelScriptPrefix parameters, the sandboxed JavaScript body, and the logging / inject_script / access_globals permission grants. |
README.md | Notes that the template is intended for use when your existing Custom HTML tag matches Medallia's standard <script type="text/javascript">...</script> installation snippet — i.e., it's a drop-in replacement, not a different integration approach. |
The repository ships only the base loader template; the consent "blind and mute" script is currently
documented here and on the original Weebly post rather than packaged as its own .tpl. If
you adapt it into an importable template, a pull request against the repository would make it easier for
others to reuse.
How to Install
- Download
Medallia Digital Experience Analytics (DXA).tplfrom the GitHub repository. - In GTM, go to Templates → New → Import (the overflow menu in the Custom Template editor) and select the
.tplfile. - Create a new tag from the imported template and fill in
decibelClientIdanddecibelInstanceIdfrom your Medallia-issued loader URL. LeavedecibelScriptPrefixat its default unless Medallia has issued you a different CDN host. - Set the trigger to your "regular" / post-consent-initialization trigger — never the container-load trigger directly, and never ahead of your consent gate.
- If you need different DXA instances per environment, swap the static
decibelInstanceIdvalue for a Lookup Table variable keyed on hostname. - Build the consent-denial template using the second script above, wire it to your consent platform's "deny" event, and confirm in GTM Preview that
decibelInsight.trackanddecibelInsight.sendEventbecome no-ops immediately after a simulated opt-out. - Publish, then verify in your network panel that
di.jsloads fromcdn.decibelinsight.netonly after consent has been granted.
This post is a Google Blogger adaptation of my original "Implementing Medallia Digital Analytics DXA from GTM", first published on my Weebly blog (DREW SPENCER) on January 16, 2026. The GTM Tag Template source — parameters, permissions, and sandboxed JavaScript — is published on GitHub at drewspen/Medallia-Digital-Experience-Analytics-DXA. Medallia Digital Experience Analytics (DXA), Decibel Insight, and Decibel by Medallia refer to the same Medallia product under its current and former names.
Comments
Post a Comment