Building Variable Templates
Variable templates are the least-discussed template type, but they solve real problems that Custom JavaScript variables struggle with. A Custom JS variable runs in the page context with access to everything — which is both its power and its maintenance liability. A variable template runs in the sandbox with declared permissions and a test suite.
If you are building a data transformation function that will be shared across teams or containers, it should be a variable template, not a Custom JavaScript variable.
Variable template vs. Custom JavaScript variable
Section titled “Variable template vs. Custom JavaScript variable”The fundamental difference: a variable template must return a value. Everything in the template code is oriented around computing and returning a single value.
| Aspect | Custom JavaScript Variable | Variable Template |
|---|---|---|
| Environment | Full browser context | Sandboxed APIs |
| Access to window/document | Yes | Via explicit APIs only |
| Testable | No built-in test runner | Yes, built-in test framework |
| Distributable | No | Yes (import/export, Gallery) |
| Best for | Quick one-off transformations | Reusable, tested transformations |
Use a variable template when:
- The variable will be used in multiple containers
- The logic is complex enough to benefit from tests
- You want to share or distribute the variable to others
- The variable accesses browser APIs (cookies, localStorage) that need permission declarations
Anatomy of a variable template
Section titled “Anatomy of a variable template”Variable template code must call return with the value you want the variable to return. The execution model is synchronous — if you need async values, you must use a callback-based pattern (covered below).
// Minimum variable template// This is the entire code sectionreturn data.someField;A more realistic example — a cookie parser:
// Template fields: cookieName (TEXT)
const getCookieValues = require('getCookieValues');
var cookieName = data.cookieName;
if (!cookieName) { return null;}
var cookieValues = getCookieValues(cookieName);return cookieValues.length > 0 ? cookieValues[0] : null;Example: URL parameter extractor
Section titled “Example: URL parameter extractor”A reusable template that extracts any URL parameter from the current page URL.
Fields:
parameterName(TEXT): the URL parameter name to extract
Code:
// URL Parameter Extractor Variable Template
const parseUrl = require('parseUrl');const copyFromWindow = require('copyFromWindow');
var paramName = data.parameterName;
if (!paramName) { return null;}
// Get the current URLvar currentUrl = copyFromWindow('location.href');if (!currentUrl) { return null;}
// Parse the URL and extract query parametersvar parsed = parseUrl(currentUrl);var search = parsed.search || '';
// Parse query string manually (parseUrl doesn't parse individual params)if (!search || search.length <= 1) { return null;}
var queryString = search.slice(1); // Remove leading '?'var pairs = queryString.split('&');
for (var i = 0; i < pairs.length; i++) { var pair = pairs[i].split('='); if (pair.length >= 2) { var key = decodeURIComponent(pair[0]); if (key === paramName) { return decodeURIComponent(pair.slice(1).join('=')); } }}
return null;Permissions needed:
access_globals:location.href(read only)
Example: Cookie parser with JSON support
Section titled “Example: Cookie parser with JSON support”Many tracking cookies store JSON-encoded values. This template parses a cookie and optionally extracts a nested path.
Fields:
cookieName(TEXT): Cookie namejsonPath(TEXT): Optional dot-notation path (e.g.,user.id)defaultValue(TEXT): Value to return if cookie is absent or path not found
Code:
const getCookieValues = require('getCookieValues');const JSON = require('JSON');const logToConsole = require('logToConsole');
var cookieName = data.cookieName;var jsonPath = data.jsonPath;var defaultValue = data.defaultValue || undefined;
if (!cookieName) { return defaultValue;}
var values = getCookieValues(cookieName);if (!values || values.length === 0) { return defaultValue;}
var rawValue = values[0];
// If no JSON path, return raw valueif (!jsonPath) { return rawValue;}
// Attempt JSON parsevar parsed;try { parsed = JSON.parse(rawValue);} catch (e) { logToConsole('Cookie parser: failed to parse JSON in cookie', cookieName); return defaultValue;}
// Navigate the JSON pathvar pathParts = jsonPath.split('.');var current = parsed;
for (var i = 0; i < pathParts.length; i++) { if (current === null || current === undefined) { return defaultValue; } current = current[pathParts[i]];}
return current !== undefined ? current : defaultValue;Note: the sandbox’s try/catch handling is different from regular JavaScript. In templates, error handling is done through the template runtime rather than try/catch. However, try/catch IS actually available in template code for catching specific runtime errors — this is one area where the sandbox is more permissive than often stated.
Example: Data transformation template
Section titled “Example: Data transformation template”Useful for applying consistent value formatting across an organization.
Fields:
inputValue(TEMPLATE): The value to transformtransformType(SELECT): currency / percentage / truncate / sanitize_pii
Code:
const makeString = require('makeString');const makeNumber = require('makeNumber');
var input = data.inputValue;var transform = data.transformType;
if (input === null || input === undefined) { return null;}
switch(transform) { case 'currency': // Convert to number, round to 2 decimal places var num = makeNumber(input); if (isNaN(num)) return null; return Math.round(num * 100) / 100;
case 'percentage': var pct = makeNumber(input); if (isNaN(pct)) return null; return Math.round(pct * 10000) / 100; // e.g., 0.1523 → 15.23
case 'truncate': var str = makeString(input); return str.length > 100 ? str.slice(0, 100) + '...' : str;
case 'sanitize_pii': // Remove email addresses from string values var text = makeString(input); return text.replace(/[a-zA-Z0-9._%+\-]+@[a-zA-Z0-9.\-]+\.[a-zA-Z]{2,}/g, '[email]');
default: return input;}Server-side variable templates with async
Section titled “Server-side variable templates with async”Server-side variable templates support asynchronous operations via Promises. This allows lookups from external services — databases, APIs, KV stores.
// Server-side variable template: Firestore user lookup// Returns the user's tier from Firestore based on user_id in the event
const getAllEventData = require('getAllEventData');const sendHttpRequest = require('sendHttpRequest');const templateDataStorage = require('templateDataStorage');const logToConsole = require('logToConsole');
const eventData = getAllEventData();const userId = eventData.user_id;
if (!userId) { return 'unknown';}
// Check cache firstvar cacheKey = 'user_tier_' + userId;var cached = templateDataStorage.getItemCopy(cacheKey);if (cached !== null && cached !== undefined) { return cached;}
// Return a Promise for async resolutionreturn new Promise(function(resolve) { var firestoreUrl = 'https://firestore.googleapis.com/v1/projects/MY_PROJECT/databases/(default)/documents/users/' + userId;
sendHttpRequest(firestoreUrl, { method: 'GET', headers: {'Authorization': 'Bearer ' + getGoogleAuth()} }, function(statusCode, headers, body) { if (statusCode !== 200) { resolve('unknown'); return; }
try { var doc = JSON.parse(body); var tier = doc.fields && doc.fields.tier && doc.fields.tier.stringValue || 'free';
// Cache the result templateDataStorage.setItemCopy(cacheKey, tier); resolve(tier); } catch(e) { logToConsole('User lookup failed:', e); resolve('unknown'); } });});Testing variable templates
Section titled “Testing variable templates”Variable templates are tested with runCode() which executes the template and captures the return value:
// Test: URL parameter extractor returns correct valuevar testUrl = 'https://example.com/page?utm_source=google&utm_medium=cpc';
mock('copyFromWindow', function(path) { if (path === 'location.href') return testUrl; return undefined;});
var result = runCode({ parameterName: 'utm_source'});
assertThat(result).isEqualTo('google');// Test: returns default value when cookie is absentmock('getCookieValues', function(name) { return []; });
var result = runCode({ cookieName: 'my_cookie', defaultValue: 'unknown'});
assertThat(result).isEqualTo('unknown');Common mistakes
Section titled “Common mistakes”Returning undefined instead of a default value. When a variable template returns undefined, the GTM variable evaluates to undefined. Set a meaningful default in the template (or use a “default value” field) rather than returning undefined for missing inputs.
Not handling the case where data.field is an empty string. In GTM, empty text fields return '' (empty string), not undefined. Check for both: if (!data.fieldName) catches both undefined and empty string.
Using synchronous return in server-side templates when async is needed. If you return synchronously but the value depends on an async operation (network request), the variable returns before the data is available. Use return new Promise(...) for any async server-side operation.